Merge "Add test_cases for cinder cli in v2 version"
diff --git a/.mailmap b/.mailmap
index 9a22c71..5c37a5e 100644
--- a/.mailmap
+++ b/.mailmap
@@ -2,6 +2,7 @@
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>
+Joe Gordon <joe.gordon0@gmail.com> <jogo@cloudscaling.com>
<brian.waldon@rackspace.com> <bcwaldon@gmail.com>
Daryl Walleck <daryl.walleck@rackspace.com> dwalleck <daryl.walleck@rackspace.com>
<jeblair@hp.com> <corvus@inaugust.com>
diff --git a/HACKING.rst b/HACKING.rst
index 7871f60..a74ff73 100644
--- a/HACKING.rst
+++ b/HACKING.rst
@@ -105,6 +105,19 @@
in tempest.api.compute would require a service tag for those services, however
they do not need to be tagged as compute.
+Negative Tests
+--------------
+When adding negative tests to tempest there are 2 requirements. First the tests
+must be marked with a negative attribute. For example::
+
+ @attr(type=negative)
+ def test_resource_no_uuid(self):
+ ...
+
+The second requirement is that all negative tests must be added to a negative
+test file. If such a file doesn't exist for the particular resource being
+tested a new test file should be added.
+
Test skips because of Known Bugs
--------------------------------
@@ -150,3 +163,32 @@
- If the execution of a set of tests is required to be serialized then locking
can be used to perform this. See AggregatesAdminTest in
tempest.api.compute.admin for an example of using locking.
+
+Stress Tests in Tempest
+-----------------------
+Any tempest test case can be flagged as a stress test. With this flag it will
+be automatically discovery and used in the stress test runs. The stress test
+framework itself is a facility to spawn and control worker processes in order
+to find race conditions (see ``tempest/stress/`` for more information). Please
+note that these stress tests can't be used for benchmarking purposes since they
+don't measure any performance characteristics.
+
+Example::
+
+ @stresstest(class_setup_per='process')
+ def test_this_and_that(self):
+ ...
+
+This will flag the test ``test_this_and_that`` as a stress test. The parameter
+``class_setup_per`` gives control when the setUpClass function should be called.
+
+Good candidates for stress tests are:
+
+- Scenario tests
+- API tests that have a wide focus
+
+Sample Configuration File
+-------------------------
+The sample config file is autogenerated using a script. If any changes are made
+to the config variables in tempest then the sample config file must be
+regenerated. This can be done running the script: tools/generate_sample.sh
diff --git a/README.rst b/README.rst
index 4161cc6..96f6e4c 100644
--- a/README.rst
+++ b/README.rst
@@ -6,6 +6,33 @@
Scenarios, and other specific tests useful in validating an OpenStack
deployment.
+Design Principles
+----------
+Tempest Design Principles that we strive to live by.
+
+- Tempest should be able to run against any OpenStack cloud, be it a
+ one node devstack install, a 20 node lxc cloud, or a 1000 node kvm
+ cloud.
+- Tempest should be explicit in testing features. It is easy to auto
+ discover features of a cloud incorrectly, and give people an
+ incorrect assessment of their cloud. Explicit is always better.
+- Tempest uses OpenStack public interfaces. Tests in Tempest should
+ only touch public interfaces, API calls (native or 3rd party),
+ public CLI or libraries.
+- Tempest should not touch private or implementation specific
+ interfaces. This means not directly going to the database, not
+ directly hitting the hypervisors, not testing extensions not
+ included in the OpenStack base. If there is some feature of
+ OpenStack that is not verifiable through standard interfaces, this
+ should be considered a possible enhancement.
+- Tempest strives for complete coverage of the OpenStack API and
+ common scenarios that demonstrate a working cloud.
+- Tempest drives load in an OpenStack cloud. By including a broad
+ array of API and scenario tests Tempest can be reused in whole or in
+ parts as load generation for an OpenStack cloud.
+- Tempest should attempt to clean up after itself, whenever possible
+ we should tear down resources when done.
+- Tempest should be self testing.
Quickstart
----------
@@ -52,6 +79,9 @@
document. The etc/tempest.conf.sample attempts to be a self
documenting version of the configuration.
+The sample config file is auto generated using the script:
+tools/generate_sample.sh
+
The most important pieces that are needed are the user ids, openstack
endpoints, and basic flavors and images needed to run tests.
diff --git a/doc/source/conf.py b/doc/source/conf.py
index cf838c0..e5444ae 100644
--- a/doc/source/conf.py
+++ b/doc/source/conf.py
@@ -51,17 +51,6 @@
project = u'Tempest'
copyright = u'2013, OpenStack QA Team'
-# The version info for the project you're documenting, acts as replacement for
-# |version| and |release|, also used in various other places throughout the
-# built documents.
-#
-# The short X.Y version.
-import pbr.version
-version_info = pbr.version.VersionInfo('tempest')
-version = version_info.version_string()
-# The full version, including alpha/beta/rc tags.
-release = version_info.release_string()
-
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#language = None
diff --git a/etc/README.txt b/etc/README.txt
deleted file mode 100644
index c7e5e6e..0000000
--- a/etc/README.txt
+++ /dev/null
@@ -1 +0,0 @@
-Copy config.ini.sample to config.ini, and update it to reflect your environment.
diff --git a/etc/TEMPEST_README.txt b/etc/TEMPEST_README.txt
deleted file mode 100644
index 50fa688..0000000
--- a/etc/TEMPEST_README.txt
+++ /dev/null
@@ -1,7 +0,0 @@
-To run:
--rename the /etc/tempest.conf.sample file to tempest.conf
--Set the fields in the file to values relevant to your system
--Set the "authentication" value (basic or keystone_v2 currently supported)
--From the root directory of the project, run "./run_tests.sh" this will
-create the venv to install the project dependencies and run nosetests tempest
-to run all the tests
diff --git a/etc/tempest.conf.sample b/etc/tempest.conf.sample
index 115a2b5..251336e 100644
--- a/etc/tempest.conf.sample
+++ b/etc/tempest.conf.sample
@@ -1,438 +1,737 @@
[DEFAULT]
-#log_config = /opt/stack/tempest/etc/logging.conf.sample
-# disable logging to the stderr
-use_stderr = False
+#
+# Options defined in tempest.openstack.common.lockutils
+#
-# log file
-log_file = tempest.log
+# Whether to disable inter-process locks (boolean value)
+#disable_process_locking=false
-# lock/semaphore base directory
-lock_path=/tmp
+# Directory to use for lock files. (string value)
+#lock_path=<None>
-default_log_levels=tempest.stress=INFO,amqplib=WARN,sqlalchemy=WARN,boto=WARN,suds=INFO,keystone=INFO,eventlet.wsgi.server=WARN
+
+#
+# Options defined in tempest.openstack.common.log
+#
+
+# Print debugging output (set logging level to DEBUG instead
+# of default WARNING level). (boolean value)
+#debug=false
+
+# Print more verbose output (set logging level to INFO instead
+# of default WARNING level). (boolean value)
+#verbose=false
+
+# Log output to standard error (boolean value)
+#use_stderr=true
+
+# format string to use for log messages with context (string
+# value)
+#logging_context_format_string=%(asctime)s.%(msecs)03d %(process)d %(levelname)s %(name)s [%(request_id)s %(user)s %(tenant)s] %(instance)s%(message)s
+
+# format string to use for log messages without context
+# (string value)
+#logging_default_format_string=%(asctime)s.%(msecs)03d %(process)d %(levelname)s %(name)s [-] %(instance)s%(message)s
+
+# data to append to log format when level is DEBUG (string
+# value)
+#logging_debug_format_suffix=%(funcName)s %(pathname)s:%(lineno)d
+
+# prefix each line of exception output with this format
+# (string value)
+#logging_exception_prefix=%(asctime)s.%(msecs)03d %(process)d TRACE %(name)s %(instance)s
+
+# list of logger=LEVEL pairs (list value)
+#default_log_levels=amqplib=WARN,sqlalchemy=WARN,boto=WARN,suds=INFO,keystone=INFO
+
+# publish error events (boolean value)
+#publish_errors=false
+
+# make deprecations fatal (boolean value)
+#fatal_deprecations=false
+
+# If an instance is passed with the log message, format it
+# like this (string value)
+#instance_format="[instance: %(uuid)s] "
+
+# If an instance UUID is passed with the log message, format
+# it like this (string value)
+#instance_uuid_format="[instance: %(uuid)s] "
+
+# If this option is specified, the logging configuration file
+# specified is used and overrides any other logging options
+# specified. Please see the Python logging module
+# documentation for details on logging configuration files.
+# (string value)
+#log_config=<None>
+
+# DEPRECATED. A logging.Formatter log message format string
+# which may use any of the available logging.LogRecord
+# attributes. This option is deprecated. Please use
+# logging_context_format_string and
+# logging_default_format_string instead. (string value)
+#log_format=<None>
+
+# Format string for %%(asctime)s in log records. Default:
+# %(default)s (string value)
+#log_date_format=%Y-%m-%d %H:%M:%S
+
+# (Optional) Name of log file to output to. If no default is
+# set, logging will go to stdout. (string value)
+# Deprecated group/name - [DEFAULT]/logfile
+#log_file=<None>
+
+# (Optional) The base directory used for relative --log-file
+# paths (string value)
+# Deprecated group/name - [DEFAULT]/logdir
+#log_dir=<None>
+
+# Use syslog for logging. (boolean value)
+#use_syslog=false
+
+# syslog facility to receive log lines (string value)
+#syslog_log_facility=LOG_USER
+
[identity]
-# This section contains configuration options that a variety of Tempest
-# test clients use when authenticating with different user/tenant
-# combinations
-# The type of endpoint for a Identity service. Unless you have a
-# custom Keystone service catalog implementation, you probably want to leave
-# this value as "identity"
-catalog_type = identity
-# Ignore SSL certificate validation failures? Use when in testing
-# environments that have self-signed SSL certs.
-disable_ssl_certificate_validation = False
-# URL for where to find the OpenStack Identity API endpoint (Keystone)
-uri = http://127.0.0.1:5000/v2.0/
-# URL for where to find the OpenStack V3 Identity API endpoint (Keystone)
-uri_v3 = http://127.0.0.1:5000/v3/
-# The identity region. Also used as the other services' region name unless
-# they are set explicitly.
-region = RegionOne
+#
+# Options defined in tempest.config
+#
-# This should be the username of a user WITHOUT administrative privileges
-username = demo
-# The above non-administrative user's password
-password = secret
-# The above non-administrative user's tenant name
-tenant_name = demo
+# Catalog type of the Identity service. (string value)
+#catalog_type=identity
-# This should be the username of an alternate user WITHOUT
-# administrative privileges
-alt_username = alt_demo
-# The above non-administrative user's password
-alt_password = secret
-# The above non-administrative user's tenant name
-alt_tenant_name = alt_demo
+# Set to True if using self-signed SSL certificates. (boolean
+# value)
+#disable_ssl_certificate_validation=false
-# This should be the username of a user WITH administrative privileges
-admin_username = admin
-# The above administrative user's password
-admin_password = secret
-# The above administrative user's tenant name
-admin_tenant_name = admin
+# Full URI of the OpenStack Identity API (Keystone), v2
+# (string value)
+#uri=<None>
-# The role that is required to administrate keystone.
-admin_role = admin
+# Full URI of the OpenStack Identity API (Keystone), v3
+# (string value)
+#uri_v3=<None>
+
+# The identity region name to use. Also used as the other
+# services' region name unless they are set explicitly. If no
+# such region is found in the service catalog, the first found
+# one is used. (string value)
+#region=RegionOne
+
+# Username to use for Nova API requests. (string value)
+#username=demo
+
+# Tenant name to use for Nova API requests. (string value)
+#tenant_name=demo
+
+# Role required to administrate keystone. (string value)
+#admin_role=admin
+
+# API key to use when authenticating. (string value)
+#password=pass
+
+# Username of alternate user to use for Nova API requests.
+# (string value)
+#alt_username=<None>
+
+# Alternate user's Tenant name to use for Nova API requests.
+# (string value)
+#alt_tenant_name=<None>
+
+# API key to use when authenticating as alternate user.
+# (string value)
+#alt_password=<None>
+
+# Administrative Username to use forKeystone API requests.
+# (string value)
+#admin_username=admin
+
+# Administrative Tenant name to use for Keystone API requests.
+# (string value)
+#admin_tenant_name=admin
+
+# API key to use when authenticating as admin. (string value)
+#admin_password=pass
+
+
+[stress]
+
+#
+# Options defined in tempest.config
+#
+
+# Directory containing log files on the compute nodes (string
+# value)
+#nova_logdir=<None>
+
+# Maximum number of instances to create during test. (integer
+# value)
+#max_instances=16
+
+# Controller host. (string value)
+#controller=<None>
+
+# Controller host. (string value)
+#target_controller=<None>
+
+# ssh user. (string value)
+#target_ssh_user=<None>
+
+# Path to private key. (string value)
+#target_private_key_path=<None>
+
+# regexp for list of log files. (string value)
+#target_logfiles=<None>
+
+# time (in seconds) between log file error checks. (integer
+# value)
+#log_check_interval=60
+
+# The number of threads created while stress test. (integer
+# value)
+#default_thread_number_per_action=4
+
+
+[image-feature-enabled]
+
+#
+# Options defined in tempest.config
+#
+
+# Is the v2 image API enabled (boolean value)
+#api_v2=true
+
+# Is the v1 image API enabled (boolean value)
+#api_v1=true
+
[compute]
-# This section contains configuration options used when executing tests
-# against the OpenStack Compute API.
-# Allows test cases to create/destroy tenants and users. This option
-# enables isolated test cases and better parallel execution,
-# but also requires that OpenStack Identity API admin credentials
-# are known.
-allow_tenant_isolation = true
+#
+# Options defined in tempest.config
+#
-# Allows test cases to create/destroy tenants and users. This option
-# enables isolated test cases and better parallel execution,
-# but also requires that OpenStack Identity API admin credentials
-# are known.
-allow_tenant_reuse = true
+# Allows test cases to create/destroy tenants and users. This
+# option enables isolated test cases and better parallel
+# execution, but also requires that OpenStack Identity API
+# admin credentials are known. (boolean value)
+#allow_tenant_isolation=false
-# Reference data for tests. The ref and ref_alt should be
-# distinct images/flavors.
-image_ref = {$IMAGE_ID}
-image_ref_alt = {$IMAGE_ID_ALT}
-flavor_ref = 1
-flavor_ref_alt = 2
+# If allow_tenant_isolation is True and a tenant that would be
+# created for a given test already exists (such as from a
+# previously-failed run), re-use that tenant instead of
+# failing because of the conflict. Note that this would result
+# in the tenant being deleted at the end of a subsequent
+# successful run. (boolean value)
+#allow_tenant_reuse=true
-# User name used to authenticate to an instance
-image_ssh_user = root
+# Valid secondary image reference to be used in tests. (string
+# value)
+#image_ref={$IMAGE_ID}
-# Password used to authenticate to an instance
-image_ssh_password = password
+# Valid secondary image reference to be used in tests. (string
+# value)
+#image_ref_alt={$IMAGE_ID_ALT}
-# User name used to authenticate to an instance using the alternate image
-image_alt_ssh_user = root
+# Valid primary flavor to use in tests. (integer value)
+#flavor_ref=1
-# Password used to authenticate to an instance using the alternate image
-image_alt_ssh_password = password
+# Valid secondary flavor to be used in tests. (integer value)
+#flavor_ref_alt=2
-# Number of seconds to wait while looping to check the status of an
-# instance that is building.
-build_interval = 10
+# User name used to authenticate to an instance. (string
+# value)
+#image_ssh_user=root
-# Number of seconds to time out on waiting for an instance
-# to build or reach an expected status
-build_timeout = 600
+# Password used to authenticate to an instance. (string value)
+#image_ssh_password=password
-# Run additional tests that use SSH for instance validation?
-# This requires the instances be routable from the host
-# executing the tests
-run_ssh = false
+# User name used to authenticate to an instance using the
+# alternate image. (string value)
+#image_alt_ssh_user=root
-# Name of a user used to authenticate to an instance.
-ssh_user = cirros
+# Password used to authenticate to an instance using the
+# alternate image. (string value)
+#image_alt_ssh_password=password
-# Visible fixed network name
-fixed_network_name = private
+# Time in seconds between build status checks. (integer value)
+#build_interval=10
-# Network id used for SSH (public, private, etc)
-network_for_ssh = public
+# Timeout in seconds to wait for an instance to build.
+# (integer value)
+#build_timeout=300
-# IP version of the address used for SSH
-ip_version_for_ssh = 4
+# Does the test environment support snapshots? (boolean value)
+#run_ssh=false
-# Number of seconds to wait to ping to an instance
-ping_timeout = 60
+# User name used to authenticate to an instance. (string
+# value)
+#ssh_user=root
-# Number of seconds to wait to authenticate to an instance
-ssh_timeout = 300
+# Timeout in seconds to wait for ping to succeed. (integer
+# value)
+#ping_timeout=60
-# Additinal wait time for clean state, when there is
-# no OS-EXT-STS extension availiable
-ready_wait = 0
+# Timeout in seconds to wait for authentication to succeed.
+# (integer value)
+#ssh_timeout=300
-# Number of seconds to wait for output from ssh channel
-ssh_channel_timeout = 60
+# Additional wait time for clean state, when there is no OS-
+# EXT-STS extension available (integer value)
+#ready_wait=0
-# Dose the SSH uses Floating IP?
-use_floatingip_for_ssh = True
+# Timeout in seconds to wait for output from ssh channel.
+# (integer value)
+#ssh_channel_timeout=60
-# The type of endpoint for a Compute API service. Unless you have a
-# custom Keystone service catalog implementation, you probably want to leave
-# this value as "compute"
-catalog_type = compute
+# Visible fixed network name (string value)
+#fixed_network_name=private
-# The name of a region for compute. If empty or commented-out, the value of
-# identity.region is used instead. If no such region is found in the service
-# catalog, the first found one is used.
-#region = RegionOne
+# Network used for SSH connections. (string value)
+#network_for_ssh=public
-# Does the Compute API support creation of images?
-create_image_enabled = true
+# IP version used for SSH connections. (integer value)
+#ip_version_for_ssh=4
-# For resize to work with libvirt/kvm, one of the following must be true:
-# Single node: allow_resize_to_same_host=True must be set in nova.conf
-# Cluster: the 'nova' user must have scp access between cluster nodes
-resize_available = true
+# Dose the SSH uses Floating IP? (boolean value)
+#use_floatingip_for_ssh=true
-# Does the compute API support changing the admin password?
-change_password_available=true
+# Catalog type of the Compute service. (string value)
+#catalog_type=compute
-# Run live migration tests (requires 2 hosts)
-live_migration_available = false
+# The compute region name to use. If empty, the value of
+# identity.region is used instead. If no such region is found
+# in the service catalog, the first found one is used. (string
+# value)
+#region=
-# Use block live migration (Otherwise, non-block migration will be
-# performed, which requires XenServer pools in case of using XS)
-use_block_migration_for_live_migration = false
+# Catalog type of the Compute v3 service. (string value)
+#catalog_v3_type=computev3
-# Supports iSCSI block migration - depends on a XAPI supporting
-# relax-xsm-sr-check
-block_migrate_supports_cinder_iscsi = false
+# Path to a private key file for SSH access to remote hosts
+# (string value)
+#path_to_private_key=<None>
-# When set to false, disk config tests are forced to skip
-disk_config_enabled = true
+# Expected device name when a volume is attached to an
+# instance (string value)
+#volume_device_name=vdb
-# When set to false, flavor extra data tests are forced to skip
-flavor_extra_enabled = true
+# Time in seconds before a shelved instance is eligible for
+# removing from a host. -1 never offload, 0 offload when
+# shelved. This time should be the same as the time of
+# nova.conf, and some tests will run for as long as the time.
+# (integer value)
+#shelved_offload_time=0
-# Expected first device name when a volume is attached to an instance
-volume_device_name = vdb
-
-[compute-admin]
-# This should be the username of a user WITH administrative privileges
-# If not defined the admin user from the identity section will be used
-username =
-# The above administrative user's password
-password =
-# The above administrative user's tenant name
-tenant_name =
-
-[image]
-# This section contains configuration options used when executing tests
-# against the OpenStack Images API
-
-# The type of endpoint for an Image API service. Unless you have a
-# custom Keystone service catalog implementation, you probably want to leave
-# this value as "image"
-catalog_type = image
-
-# The name of a region for image. If empty or commented-out, the value of
-# identity.region is used instead. If no such region is found in the service
-# catalog, the first found one is used.
-#region = RegionOne
-
-# The version of the OpenStack Images API to use
-api_version = 1
-
-# HTTP image to use for glance http image testing
-http_image = http://download.cirros-cloud.net/0.3.1/cirros-0.3.1-x86_64-uec.tar.gz
[network]
-# This section contains configuration options used when executing tests
-# against the OpenStack Network API.
-# Version of the Neutron API
-api_version = v1.1
-# Catalog type of the Neutron Service
-catalog_type = network
+#
+# Options defined in tempest.config
+#
-# The name of a region for network. If empty or commented-out, the value of
-# identity.region is used instead. If no such region is found in the service
-# catalog, the first found one is used.
-#region = RegionOne
+# Catalog type of the Neutron service. (string value)
+#catalog_type=network
-# A large private cidr block from which to allocate smaller blocks for
-# tenant networks.
-tenant_network_cidr = 10.100.0.0/16
+# The network region name to use. If empty, the value of
+# identity.region is used instead. If no such region is found
+# in the service catalog, the first found one is used. (string
+# value)
+#region=
-# The mask bits used to partition the tenant block.
-tenant_network_mask_bits = 24
+# The cidr block to allocate tenant networks from (string
+# value)
+#tenant_network_cidr=10.100.0.0/16
-# If tenant networks are reachable, connectivity checks will be
-# performed directly against addresses on those networks.
-tenant_networks_reachable = false
+# The mask bits for tenant networks (integer value)
+#tenant_network_mask_bits=28
-# Id of the public network that provides external connectivity.
-public_network_id = {$PUBLIC_NETWORK_ID}
+# Whether tenant network connectivity should be evaluated
+# directly (boolean value)
+#tenant_networks_reachable=false
-# Id of a shared public router that provides external connectivity.
-# A shared public router would commonly be used where IP namespaces
-# were disabled. If namespaces are enabled, it would be preferable
-# for each tenant to have their own router.
-public_router_id = {$PUBLIC_ROUTER_ID}
+# Id of the public network that provides external connectivity
+# (string value)
+#public_network_id=
+
+# Id of the public router that provides external connectivity
+# (string value)
+#public_router_id=
+
+
+[boto]
+
+#
+# Options defined in tempest.config
+#
+
+# EC2 URL (string value)
+#ec2_url=http://localhost:8773/services/Cloud
+
+# S3 URL (string value)
+#s3_url=http://localhost:8080
+
+# AWS Secret Key (string value)
+#aws_secret=<None>
+
+# AWS Access Key (string value)
+#aws_access=<None>
+
+# S3 Materials Path (string value)
+#s3_materials_path=/opt/stack/devstack/files/images/s3-materials/cirros-0.3.0
+
+# ARI Ramdisk Image manifest (string value)
+#ari_manifest=cirros-0.3.0-x86_64-initrd.manifest.xml
+
+# AMI Machine Image manifest (string value)
+#ami_manifest=cirros-0.3.0-x86_64-blank.img.manifest.xml
+
+# AKI Kernel Image manifest (string value)
+#aki_manifest=cirros-0.3.0-x86_64-vmlinuz.manifest.xml
+
+# Instance type (string value)
+#instance_type=m1.tiny
+
+# boto Http socket timeout (integer value)
+#http_socket_timeout=3
+
+# boto num_retries on error (integer value)
+#num_retries=1
+
+# Status Change Timeout (integer value)
+#build_timeout=60
+
+# Status Change Test Interval (integer value)
+#build_interval=1
+
+
+[scenario]
+
+#
+# Options defined in tempest.config
+#
+
+# Directory containing image files (string value)
+#img_dir=/opt/stack/new/devstack/files/images/cirros-0.3.1-x86_64-uec
+
+# AMI image file name (string value)
+#ami_img_file=cirros-0.3.1-x86_64-blank.img
+
+# ARI image file name (string value)
+#ari_img_file=cirros-0.3.1-x86_64-initrd
+
+# AKI image file name (string value)
+#aki_img_file=cirros-0.3.1-x86_64-vmlinuz
+
+# ssh username for the image file (string value)
+#ssh_user=cirros
+
+# specifies how many resources to request at once. Used for
+# large operations testing. (integer value)
+#large_ops_number=0
+
+
+[image]
+
+#
+# Options defined in tempest.config
+#
+
+# Catalog type of the Image service. (string value)
+#catalog_type=image
+
+# The image region name to use. If empty, the value of
+# identity.region is used instead. If no such region is found
+# in the service catalog, the first found one is used. (string
+# value)
+#region=
+
+# http accessible image (string value)
+#http_image=http://download.cirros-cloud.net/0.3.1/cirros-0.3.1-x86_64-uec.tar.gz
+
+
+[compute-admin]
+
+#
+# Options defined in tempest.config
+#
+
+# Administrative Username to use for Nova API requests.
+# (string value)
+#username=admin
+
+# Administrative Tenant name to use for Nova API requests.
+# (string value)
+#tenant_name=admin
+
+# API key to use when authenticating as admin. (string value)
+#password=pass
+
+
+[cli]
+
+#
+# Options defined in tempest.cli
+#
+
+# enable cli tests (boolean value)
+#enabled=true
+
+# directory where python client binaries are located (string
+# value)
+#cli_dir=/usr/local/bin
+
+# Number of seconds to wait on a CLI timeout (integer value)
+#timeout=15
[volume]
-# This section contains the configuration options used when executing tests
-# against the OpenStack Block Storage API service
-# The type of endpoint for a Cinder or Block Storage API service.
-# Unless you have a custom Keystone service catalog implementation, you
-# probably want to leave this value as "volume"
-catalog_type = volume
-# The name of a region for volume. If empty or commented-out, the value of
-# identity.region is used instead. If no such region is found in the service
-# catalog, the first found one is used.
-#region = RegionOne
-# The disk format to use when copying a volume to image
-disk_format = raw
-# Number of seconds to wait while looping to check the status of a
-# volume that is being made available
-build_interval = 10
-# Number of seconds to time out on waiting for a volume
-# to be available or reach an expected status
-build_timeout = 300
-# Runs Cinder multi-backend tests (requires 2 backends declared in cinder.conf)
-# They must have different volume_backend_name (backend1_name and backend2_name
-# have to be different)
-multi_backend_enabled = false
-backend1_name = BACKEND_1
-backend2_name = BACKEND_2
-# Protocol and vendor of volume backend to target when testing volume-types.
-# You should update to reflect those exported by configured backend driver.
-storage_protocol = iSCSI
-vendor_name = Open Source
+#
+# Options defined in tempest.config
+#
-[object-storage]
-# This section contains configuration options used when executing tests
-# against the OpenStack Object Storage API.
+# Time in seconds between volume availability checks. (integer
+# value)
+#build_interval=10
-# You can configure the credentials in the compute section
+# Timeout in seconds to wait for a volume to becomeavailable.
+# (integer value)
+#build_timeout=300
-# The type of endpoint for an Object Storage API service. Unless you have a
-# custom Keystone service catalog implementation, you probably want to leave
-# this value as "object-store"
-catalog_type = object-store
+# Catalog type of the Volume Service (string value)
+#catalog_type=volume
-# The name of a region for object storage. If empty or commented-out, the
-# value of identity.region is used instead. If no such region is found in
-# the service catalog, the first found one is used.
-#region = RegionOne
+# The volume region name to use. If empty, the value of
+# identity.region is used instead. If no such region is found
+# in the service catalog, the first found one is used. (string
+# value)
+#region=
-# Number of seconds to time on waiting for a container to container
-# synchronization complete
-container_sync_timeout = 120
-# Number of seconds to wait while looping to check the status of a
-# container to container synchronization
-container_sync_interval = 5
-# Set to True if the Account Quota middleware is enabled
-accounts_quotas_available = True
-# Set to True if the Container Quota middleware is enabled
-container_quotas_available = True
+# Name of the backend1 (must be declared in cinder.conf)
+# (string value)
+#backend1_name=BACKEND_1
-# Set operator role for tests that require creating a container
-operator_role = Member
+# Name of the backend2 (must be declared in cinder.conf)
+# (string value)
+#backend2_name=BACKEND_2
-[boto]
-# This section contains configuration options used when executing tests
-# with boto.
+# Backend protocol to target when creating volume types
+# (string value)
+#storage_protocol=iSCSI
-# EC2 URL
-ec2_url = http://localhost:8773/services/Cloud
-# S3 URL
-s3_url = http://localhost:3333
+# Backend vendor to target when creating volume types (string
+# value)
+#vendor_name=Open Source
-# Use keystone ec2-* command to get those values for your test user and tenant
-aws_access =
-aws_secret =
+# Disk format to use when copying a volume to image (string
+# value)
+#disk_format=raw
-# Image materials for S3 upload
-# ALL content of the specified directory will be uploaded to S3
-s3_materials_path = /opt/stack/devstack/files/images/s3-materials/cirros-0.3.1
-
-# The manifest.xml files, must be in the s3_materials_path directory
-# Subdirectories not allowed!
-# The filenames will be used as a Keys in the S3 Buckets
-
-# ARI Ramdisk manifest. Must be in the above s3_materials_path
-ari_manifest = cirros-0.3.1-x86_64-initrd.manifest.xml
-
-# AMI Machine Image manifest. Must be in the above s3_materials_path
-ami_manifest = cirros-0.3.1-x86_64-blank.img.manifest.xml
-
-# AKI Kernel Image manifest, Must be in the above s3_materials_path
-aki_manifest = cirros-0.3.1-x86_64-vmlinuz.manifest.xml
-
-# Instance type
-instance_type = m1.tiny
-
-# TCP/IP connection timeout
-http_socket_timeout = 5
-
-# Number of retries actions on connection or 5xx error
-num_retries = 1
-
-# Status change wait timout
-build_timeout = 120
-
-# Status change wait interval
-build_interval = 1
-
-[orchestration]
-# The type of endpoint for an Orchestration API service. Unless you have a
-# custom Keystone service catalog implementation, you probably want to leave
-# this value as "orchestration"
-catalog_type = orchestration
-
-# The name of a region for orchestration. If empty or commented-out, the value
-# of identity.region is used instead. If no such region is found in the service
-# catalog, the first found one is used.
-#region = RegionOne
-
-# Status change wait interval
-build_interval = 1
-
-# Status change wait timout. This may vary across environments as some some
-# tests spawn full VMs, which could be slow if the test is already in a VM.
-build_timeout = 300
-
-# Instance type for tests. Needs to be big enough for a
-# full OS plus the test workload
-instance_type = m1.micro
-
-# Name of heat-cfntools enabled image to use when launching test instances
-# If not specified, tests that spawn instances will not run
-#image_ref = ubuntu-vm-heat-cfntools
-
-# Name of existing keypair to launch servers with. The default is not to specify
-# any key, which will generate a keypair for each test class
-#keypair_name = heat_key
[dashboard]
-# URL where to find the dashboard home page
-dashboard_url = 'http://localhost/'
-# URL where to submit the login form
-login_url = 'http://localhost/auth/login/'
+#
+# Options defined in tempest.config
+#
-[scenario]
-# Directory containing image files
-img_dir = /opt/stack/new/devstack/files/images/cirros-0.3.1-x86_64-uec
+# Where the dashboard can be found (string value)
+#dashboard_url=http://localhost/
-# AMI image file name
-ami_img_file = cirros-0.3.1-x86_64-blank.img
+# Login page for the dashboard (string value)
+#login_url=http://localhost/auth/login/
-# ARI image file name
-ari_img_file = cirros-0.3.1-x86_64-initrd
-# AKI image file name
-aki_img_file = cirros-0.3.1-x86_64-vmlinuz
+[orchestration]
-# ssh username for the image file
-ssh_user = cirros
+#
+# Options defined in tempest.config
+#
-# specifies how many resources to request at once. Used for large operations
-# testing."
-large_ops_number = 0
+# Catalog type of the Orchestration service. (string value)
+#catalog_type=orchestration
-[cli]
-# Enable cli tests
-enabled = True
-# directory where python client binaries are located
-cli_dir = /usr/local/bin
-# Number of seconds to wait on a CLI timeout
-timeout = 15
+# The orchestration region name to use. If empty, the value of
+# identity.region is used instead. If no such region is found
+# in the service catalog, the first found one is used. (string
+# value)
+#region=
-[service_available]
-# Whether or not cinder is expected to be available
-cinder = True
-# Whether or not neutron is expected to be available
-neutron = false
-# Whether or not glance is expected to be available
-glance = True
-# Whether or not swift is expected to be available
-swift = True
-# Whether or not nova is expected to be available
-nova = True
-# Whether or not Heat is expected to be available
-heat = false
-# Whether or not horizon is expected to be available
-horizon = True
+# Allows test cases to create/destroy tenants and users. This
+# option enables isolated test cases and better parallel
+# execution, but also requires that OpenStack Identity API
+# admin credentials are known. (boolean value)
+#allow_tenant_isolation=false
-[stress]
-# Maximum number of instances to create during test
-max_instances = 32
-# Time (in seconds) between log file error checks
-log_check_interval = 60
-# The default number of threads created while stress test
-default_thread_number_per_action=4
+# Time in seconds between build status checks. (integer value)
+#build_interval=1
+
+# Timeout in seconds to wait for a stack to build. (integer
+# value)
+#build_timeout=300
+
+# Instance type for tests. Needs to be big enough for a full
+# OS plus the test workload (string value)
+#instance_type=m1.micro
+
+# Name of heat-cfntools enabled image to use when launching
+# test instances. (string value)
+#image_ref=<None>
+
+# Name of existing keypair to launch servers with. (string
+# value)
+#keypair_name=<None>
+
+# Value must match heat configuration of the same name.
+# (integer value)
+#max_template_size=524288
+
+
+[object-storage]
+
+#
+# Options defined in tempest.config
+#
+
+# Catalog type of the Object-Storage service. (string value)
+#catalog_type=object-store
+
+# The object-storage region name to use. If empty, the value
+# of identity.region is used instead. If no such region is
+# found in the service catalog, the first found one is used.
+# (string value)
+#region=
+
+# Number of seconds to time on waiting for a containerto
+# container synchronization complete. (integer value)
+#container_sync_timeout=120
+
+# Number of seconds to wait while looping to check thestatus
+# of a container to container synchronization (integer value)
+#container_sync_interval=5
+
+# Role to add to users created for swift tests to enable
+# creating containers (string value)
+#operator_role=Member
+
[debug]
-# Enable diagnostic commands
-enable = True
+
+#
+# Options defined in tempest.config
+#
+
+# Enable diagnostic commands (boolean value)
+#enable=true
+
+
+[service_available]
+
+#
+# Options defined in tempest.config
+#
+
+# Whether or not cinder is expected to be available (boolean
+# value)
+#cinder=true
+
+# Whether or not neutron is expected to be available (boolean
+# value)
+#neutron=false
+
+# Whether or not glance is expected to be available (boolean
+# value)
+#glance=true
+
+# Whether or not swift is expected to be available (boolean
+# value)
+#swift=true
+
+# Whether or not nova is expected to be available (boolean
+# value)
+#nova=true
+
+# Whether or not Heat is expected to be available (boolean
+# value)
+#heat=false
+
+# Whether or not Ceilometer is expected to be available
+# (boolean value)
+#ceilometer=true
+
+# Whether or not Horizon is expected to be available (boolean
+# value)
+#horizon=true
+
+
+[compute-feature-enabled]
+
+#
+# Options defined in tempest.config
+#
+
+# If false, skip all nova v3 tests. (boolean value)
+#api_v3=true
+
+# If false, skip disk config tests (boolean value)
+#disk_config=true
+
+# If false, skip flavor extra data test (boolean value)
+#flavor_extra=true
+
+# Does the test environment support changing the admin
+# password? (boolean value)
+#change_password=false
+
+# Does the test environment support snapshots? (boolean value)
+#create_image=false
+
+# Does the test environment support resizing? (boolean value)
+#resize=false
+
+# Does the test environment support live migration available?
+# (boolean value)
+#live_migration=false
+
+# Does the test environment use block devices for live
+# migration (boolean value)
+#block_migration_for_live_migration=false
+
+# Does the test environment block migration support cinder
+# iSCSI volumes (boolean value)
+#block_migrate_cinder_iscsi=false
+
+
+[object-storage-feature-enabled]
+
+#
+# Options defined in tempest.config
+#
+
+# Set to True if the container quota middleware is enabled
+# (boolean value)
+#container_quotas=true
+
+# Set to True if the Account Quota middleware is enabled
+# (boolean value)
+#accounts_quotas=true
+
+# Set to True if the Crossdomain middleware is enabled
+# (boolean value)
+#crossdomain=true
+
+
+[volume-feature-enabled]
+
+#
+# Options defined in tempest.config
+#
+
+# Runs Cinder multi-backend test (requires 2 backends)
+# (boolean value)
+#multi_backend=false
+
+
diff --git a/etc/whitelist.yaml b/etc/whitelist.yaml
new file mode 100644
index 0000000..24ee5e1
--- /dev/null
+++ b/etc/whitelist.yaml
@@ -0,0 +1,208 @@
+n-cpu:
+ - module: "nova.virt.libvirt.driver"
+ message: "During wait destroy, instance disappeared"
+ - module: "glanceclient.common.http"
+ message: "Request returned failure status"
+ - module: "nova.openstack.common.periodic_task"
+ message: "Error during ComputeManager\\.update_available_resource: \
+ 'NoneType' object is not iterable"
+ - module: "nova.compute.manager"
+ message: "Possibly task preempted"
+ - module: "nova.openstack.common.rpc.amqp"
+ message: "Exception during message handling"
+ - module: "nova.network.api"
+ message: "Failed storing info cache"
+ - module: "nova.compute.manager"
+ message: "Error while trying to clean up image"
+ - module: "nova.virt.libvirt.driver"
+ message: "Error injecting data into image.*\\(Unexpected error while \
+ running command"
+ - module: "nova.compute.manager"
+ message: "Instance failed to spawn"
+ - module: "nova.compute.manager"
+ message: "Error: Unexpected error while running command"
+ - module: "nova.virt.libvirt.driver"
+ message: "Error from libvirt during destroy"
+ - module: "nova.virt.libvirt.vif"
+ message: "Failed while unplugging vif"
+ - module: "nova.openstack.common.loopingcal"
+ message: "in fixed duration looping call"
+ - module: "nova.virt.libvirt.driver"
+ message: "Getting disk size of instance"
+ - module: "nova.virt.libvirt.driver"
+ message: "No such file or directory: '/opt/stack/data/nova/instances"
+ - module: "nova.compute.manager"
+ message: "error during stop\\(\\) in sync_power_state"
+ - module: "nova.compute.manager"
+ message: "Instance failed network setup after 1 attempt"
+ - module: "nova.compute.manager"
+ message: "Periodic sync_power_state task had an error"
+
+g-api:
+ - module: "glance.store.sheepdog"
+ message: "Error in store configuration: Unexpected error while \
+ running command"
+ - module: "swiftclient"
+ message: "Container HEAD failed: .*404 Not Found"
+ - module: "glance.api.middleware.cache"
+ message: "however the registry did not contain metadata for that image"
+
+ceilometer-acompute:
+ - module: "ceilometer.compute.pollsters.disk"
+ message: "Unable to read from monitor: Connection reset by peer"
+ - module: "ceilometer.compute.pollsters.disk"
+ message: "Requested operation is not valid: domain is not running"
+ - module: "ceilometer.compute.pollsters.net"
+ message: "Requested operation is not valid: domain is not running"
+ - module: "ceilometer.compute.pollsters.disk"
+ message: "Domain not found: no domain with matching uuid"
+ - module: "ceilometer.compute.pollsters.net"
+ message: "No module named libvirt"
+ - module: "ceilometer.compute.pollsters.net"
+ message: "Unable to write to monitor: Broken pipe"
+ - module: "ceilometer.compute.pollsters.cpu"
+ message: "Domain not found: no domain with matching uuid"
+
+ceilometer-alarm-evaluator:
+ - module: "ceilometer.alarm.service"
+ message: "alarm evaluation cycle failed"
+
+h-api:
+ - module: "root"
+ message: "Returning 400 to user: The server could not comply with \
+ the request since it is either malformed or otherwise incorrect"
+ - module: "root"
+ message: "Unexpected error occurred serving API: Request limit \
+ exceeded: Template exceeds maximum allowed size"
+ - module: "root"
+ message: "Unexpected error occurred serving API: The Stack \
+ .*could not be found"
+
+h-eng:
+ - module: "heat.openstack.common.rpc.amqp"
+ message: "Exception during message handling"
+ - module: "heat.openstack.common.rpc.common"
+ message: "The Stack .* could not be found"
+
+n-api:
+ - module: "glanceclient.common.http"
+ message: "Request returned failure status"
+ - module: "nova.api.openstack"
+ message: "Caught error: Quota exceeded for"
+ - module: "nova.compute.api"
+ message: "ServerDiskConfigTest"
+ - module: "nova.compute.api"
+ message: "ServersTest"
+ - module: "nova.compute.api"
+ message: "\\{u'kernel_id'.*u'ramdisk_id':"
+ - module: "nova.api.openstack.wsgi"
+ message: "takes exactly 4 arguments"
+ - module: "nova.api.openstack"
+ message: "Caught error: Instance .* could not be found"
+ - module: "nova.api.metadata.handler"
+ message: "Failed to get metadata for instance id:"
+
+n-cond:
+ - module: "nova.notifications"
+ message: "Failed to send state update notification"
+ - module: "nova.openstack.common.rpc.amqp"
+ message: "Exception during message handling"
+ - module: "nova.openstack.common.rpc.common"
+ message: "but the actual state is deleting to caller"
+ - module: "nova.openstack.common.rpc.common"
+ message: "Traceback \\(most recent call last"
+
+n-sch:
+ - module: "nova.scheduler.filter_scheduler"
+ message: "Error from last host: "
+
+n-net:
+ - module: "nova.openstack.common.rpc.amqp"
+ message: "Exception during message handling"
+ - module: "nova.openstack.common.rpc.common"
+ message: "'NoneType' object has no attribute '__getitem__'"
+
+c-api:
+ - module: "cinder.api.middleware.fault"
+ message: "Caught error: Volume .* could not be found"
+ - module: "cinder.api.middleware.fault"
+ message: "Caught error: Snapshot .* could not be found"
+ - module: "cinder.api.openstack.wsgi"
+ message: "argument must be a string or a number, not 'NoneType'"
+ - module: "cinder.volume.api"
+ message: "Volume status must be available to reserve"
+
+c-vol:
+ - module: "cinder.brick.iscsi.iscsi"
+ message: "Failed to create iscsi target for volume id"
+ - module: "cinder.brick.local_dev.lvm"
+ message: "/dev/dm-1: stat failed: No such file or directory"
+ - module: "cinder.brick.local_dev.lvm"
+ message: "LV stack-volumes.*in use: not deactivating"
+ - module: "cinder.brick.local_dev.lvm"
+ message: "Can't remove open logical volume"
+
+q-dhpc:
+ - module: "neutron.common.legacy"
+ message: "Skipping unknown group key: firewall_driver"
+ - module: "neutron.agent.dhcp_agent"
+ message: "Unable to enable dhcp"
+ - module: "neutron.agent.dhcp_agent"
+ message: " Network .* RPC info call failed"
+
+ceilometer-collector:
+ - module: "stevedore.extension"
+ message: ".*"
+ - module: "ceilometer.collector.dispatcher.database"
+ message: "duplicate key value violates unique constraint"
+ - module: "ceilometer.collector.dispatcher.database"
+ message: "Failed to record metering data: QueuePool limit"
+ - module: "ceilometer.collector.dispatcher.database"
+ message: "Failed to record metering data: .* integer out of range"
+ - module: "ceilometer.collector.dispatcher.database"
+ message: "Failed to record metering data: .* integer out of range"
+ - module: "ceilometer.openstack.common.db.sqlalchemy.session"
+ message: "DB exception wrapped"
+
+q-agt:
+ - module: "neutron.agent.linux.ovs_lib"
+ message: "Unable to execute.*Exception:"
+
+q-dhcp:
+ - module: "neutron.common.legacy"
+ message: "Skipping unknown group key: firewall_driver"
+ - module: "neutron.agent.dhcp_agent"
+ message: "Unable to enable dhcp"
+ - module: "neutron.agent.dhcp_agent"
+ message: "Network .* RPC info call failed"
+
+q-l3:
+ - module: "neutron.common.legacy"
+ message: "Skipping unknown group key: firewall_driver"
+ - module: "neutron.agent.l3_agent"
+ message: "Failed synchronizing routers"
+
+q-vpn:
+ - module: "neutron.common.legacy"
+ message: "Skipping unknown group key: firewall_driver"
+
+q-lbaas:
+ - module: "neutron.common.legacy"
+ message: "Skipping unknown group key: firewall_driver"
+ - module: "neutron.services.loadbalancer.drivers.haproxy.agent_manager"
+ message: "Error upating stats"
+ - module: "neutron.services.loadbalancer.drivers.haproxy.agent_manager"
+ message: "Unable to destroy device for pool"
+
+q-svc:
+ - module: "neutron.common.legacy"
+ message: "Skipping unknown group key: firewall_driver"
+ - module: "neutron.openstack.common.rpc.amqp"
+ message: "Exception during message handling"
+ - module: "neutron.openstack.common.rpc.common"
+ message: "(Network|Pool|Subnet|Agent|Port) .* could not be found"
+ - module: "neutron.api.v2.resource"
+ message: ".* failed"
+ - module: ".*"
+ message: ".*"
+
diff --git a/include/sample_vm/README.txt b/include/sample_vm/README.txt
deleted file mode 100644
index 51b609d..0000000
--- a/include/sample_vm/README.txt
+++ /dev/null
@@ -1,6 +0,0 @@
-You will need to download an image into this directory..
-Will also need to update the tests to reference this new image.
-
-You could use e.g. the Ubuntu Natty cloud images (this matches the sample configuration):
-$ wget http://cloud-images.ubuntu.com/releases/natty/release/ubuntu-11.04-server-cloudimg-amd64.tar.gz
-$ tar xvzf ubuntu-11.04-server-cloudimg-amd64.tar.gz
diff --git a/include/swift_objects/README.txt b/include/swift_objects/README.txt
deleted file mode 100644
index 3857524..0000000
--- a/include/swift_objects/README.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-## For the swift tests you will need three objects to upload for the test
-## examples below are a 512K object, a 500M object, and 1G object
-dd if=/dev/zero of=swift_small bs=512 count=1024
-dd if=/dev/zero of=swift_medium bs=512 count=1024000
-dd if=/dev/zero of=swift_large bs=1024 count=1024000
diff --git a/openstack-common.conf b/openstack-common.conf
index ff84404..38d58ee 100644
--- a/openstack-common.conf
+++ b/openstack-common.conf
@@ -1,10 +1,12 @@
[DEFAULT]
# The list of modules to copy from openstack-common
+module=config
module=install_venv_common
module=lockutils
module=log
module=importlib
+module=fixture
# The base module to hold the copy of openstack.common
base=tempest
diff --git a/requirements.txt b/requirements.txt
index 3b9ec44..4f6a1d3 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -5,20 +5,20 @@
jsonschema>=1.3.0,!=1.4.0
testtools>=0.9.32
lxml>=2.3
-boto>=2.4.0
+boto>=2.4.0,!=2.13.0
paramiko>=1.8.0
netaddr
python-glanceclient>=0.9.0
-python-keystoneclient>=0.3.0
-python-novaclient>=2.12.0
-python-neutronclient>=2.2.3,<3
-python-cinderclient>=1.0.4
+python-keystoneclient>=0.4.1
+python-novaclient>=2.15.0
+python-neutronclient>=2.3.0,<3
+python-cinderclient>=1.0.6
python-heatclient>=0.2.3
testresources>=0.2.4
-keyring>=1.6.1
+keyring>=1.6.1,<2.0
testrepository>=0.0.17
oslo.config>=1.2.0
eventlet>=0.13.0
-six<1.4.0
+six>=1.4.1
iso8601>=0.1.4
fixtures>=0.3.14
diff --git a/run_tests.sh b/run_tests.sh
index 970da51..5c8ce7d 100755
--- a/run_tests.sh
+++ b/run_tests.sh
@@ -63,7 +63,7 @@
-l|--logging) logging=1;;
-L|--logging-config) logging_config=$2; shift;;
--) [ "yes" == "$first_uu" ] || testrargs="$testrargs $1"; first_uu=no ;;
- *) testrargs="$testrargs $1"
+ *) testrargs="$testrargs $1"; noseargs+=" $1" ;;
esac
shift
done
diff --git a/setup.cfg b/setup.cfg
index a4cf118..23a97ff 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,6 +1,6 @@
[metadata]
name = tempest
-version = 2013.2
+version = 2014.1
summary = OpenStack Integration Testing
description-file =
README.rst
@@ -17,20 +17,7 @@
Programming Language :: Python :: 2
Programming Language :: Python :: 2.7
-[global]
-setup-hooks =
- pbr.hooks.setup_hook
-
[build_sphinx]
all_files = 1
build-dir = doc/build
source-dir = doc/source
-
-[nosetests]
-# NOTE(jkoelker) To run the test suite under nose install the following
-# coverage http://pypi.python.org/pypi/coverage
-# openstack-nose https://github.com/openstack-dev/openstack-nose
-verbosity=2
-
-[pbr]
-autodoc_tree_index_modules=true
diff --git a/setup.py b/setup.py
index 59a0090..70c2b3f 100755
--- a/setup.py
+++ b/setup.py
@@ -14,8 +14,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+# THIS FILE IS MANAGED BY THE GLOBAL REQUIREMENTS REPO - DO NOT EDIT
import setuptools
setuptools.setup(
- setup_requires=['d2to1', 'pbr'],
- d2to1=True)
+ setup_requires=['pbr'],
+ pbr=True)
diff --git a/tempest/api/compute/__init__.py b/tempest/api/compute/__init__.py
index 2c21740..a528754 100644
--- a/tempest/api/compute/__init__.py
+++ b/tempest/api/compute/__init__.py
@@ -22,11 +22,11 @@
LOG = logging.getLogger(__name__)
CONFIG = config.TempestConfig()
-CREATE_IMAGE_ENABLED = CONFIG.compute.create_image_enabled
-RESIZE_AVAILABLE = CONFIG.compute.resize_available
-CHANGE_PASSWORD_AVAILABLE = CONFIG.compute.change_password_available
-DISK_CONFIG_ENABLED = CONFIG.compute.disk_config_enabled
-FLAVOR_EXTRA_DATA_ENABLED = CONFIG.compute.flavor_extra_enabled
+CREATE_IMAGE_ENABLED = CONFIG.compute_feature_enabled.create_image
+RESIZE_AVAILABLE = CONFIG.compute_feature_enabled.resize
+CHANGE_PASSWORD_AVAILABLE = CONFIG.compute_feature_enabled.change_password
+DISK_CONFIG_ENABLED = CONFIG.compute_feature_enabled.disk_config
+FLAVOR_EXTRA_DATA_ENABLED = CONFIG.compute_feature_enabled.flavor_extra
MULTI_USER = True
diff --git a/tempest/api/compute/admin/test_aggregates.py b/tempest/api/compute/admin/test_aggregates.py
index 14ab236..467a6f9 100644
--- a/tempest/api/compute/admin/test_aggregates.py
+++ b/tempest/api/compute/admin/test_aggregates.py
@@ -17,12 +17,12 @@
from tempest.api.compute import base
from tempest.common import tempest_fixtures as fixtures
-from tempest.common.utils.data_utils import rand_name
+from tempest.common.utils import data_utils
from tempest import exceptions
from tempest.test import attr
-class AggregatesAdminTestJSON(base.BaseComputeAdminTest):
+class AggregatesAdminTestJSON(base.BaseV2ComputeAdminTest):
"""
Tests Aggregates API that require admin privileges
@@ -47,7 +47,7 @@
@attr(type='gate')
def test_aggregate_create_delete(self):
# Create and delete an aggregate.
- aggregate_name = rand_name(self.aggregate_name_prefix)
+ aggregate_name = data_utils.rand_name(self.aggregate_name_prefix)
resp, aggregate = self.client.create_aggregate(aggregate_name)
self.assertEqual(200, resp.status)
self.assertEqual(aggregate_name, aggregate['name'])
@@ -60,8 +60,8 @@
@attr(type='gate')
def test_aggregate_create_delete_with_az(self):
# Create and delete an aggregate.
- aggregate_name = rand_name(self.aggregate_name_prefix)
- az_name = rand_name(self.az_name_prefix)
+ aggregate_name = data_utils.rand_name(self.aggregate_name_prefix)
+ az_name = data_utils.rand_name(self.az_name_prefix)
resp, aggregate = self.client.create_aggregate(aggregate_name, az_name)
self.assertEqual(200, resp.status)
self.assertEqual(aggregate_name, aggregate['name'])
@@ -74,7 +74,7 @@
@attr(type='gate')
def test_aggregate_create_verify_entry_in_list(self):
# Create an aggregate and ensure it is listed.
- aggregate_name = rand_name(self.aggregate_name_prefix)
+ aggregate_name = data_utils.rand_name(self.aggregate_name_prefix)
resp, aggregate = self.client.create_aggregate(aggregate_name)
self.addCleanup(self.client.delete_aggregate, aggregate['id'])
@@ -85,9 +85,9 @@
aggregates))
@attr(type='gate')
- def test_aggregate_create_get_details(self):
+ def test_aggregate_create_update_metadata_get_details(self):
# Create an aggregate and ensure its details are returned.
- aggregate_name = rand_name(self.aggregate_name_prefix)
+ aggregate_name = data_utils.rand_name(self.aggregate_name_prefix)
resp, aggregate = self.client.create_aggregate(aggregate_name)
self.addCleanup(self.client.delete_aggregate, aggregate['id'])
@@ -96,13 +96,25 @@
self.assertEqual(aggregate['name'], body['name'])
self.assertEqual(aggregate['availability_zone'],
body['availability_zone'])
+ self.assertEqual({}, body["metadata"])
+
+ # set the metadata of the aggregate
+ meta = {"key": "value"}
+ resp, body = self.client.set_metadata(aggregate['id'], meta)
+ self.assertEqual(200, resp.status)
+ self.assertEqual(meta, body["metadata"])
+
+ # verify the metadata has been set
+ resp, body = self.client.get_aggregate(aggregate['id'])
+ self.assertEqual(200, resp.status)
+ self.assertEqual(meta, body["metadata"])
@attr(type='gate')
def test_aggregate_create_update_with_az(self):
# Update an aggregate and ensure properties are updated correctly
self.useFixture(fixtures.LockFixture('availability_zone'))
- aggregate_name = rand_name(self.aggregate_name_prefix)
- az_name = rand_name(self.az_name_prefix)
+ aggregate_name = data_utils.rand_name(self.aggregate_name_prefix)
+ az_name = data_utils.rand_name(self.az_name_prefix)
resp, aggregate = self.client.create_aggregate(aggregate_name, az_name)
self.addCleanup(self.client.delete_aggregate, aggregate['id'])
@@ -132,7 +144,7 @@
@attr(type=['negative', 'gate'])
def test_aggregate_create_as_user(self):
# Regular user is not allowed to create an aggregate.
- aggregate_name = rand_name(self.aggregate_name_prefix)
+ aggregate_name = data_utils.rand_name(self.aggregate_name_prefix)
self.assertRaises(exceptions.Unauthorized,
self.user_client.create_aggregate,
aggregate_name)
@@ -140,7 +152,7 @@
@attr(type=['negative', 'gate'])
def test_aggregate_delete_as_user(self):
# Regular user is not allowed to delete an aggregate.
- aggregate_name = rand_name(self.aggregate_name_prefix)
+ aggregate_name = data_utils.rand_name(self.aggregate_name_prefix)
resp, aggregate = self.client.create_aggregate(aggregate_name)
self.addCleanup(self.client.delete_aggregate, aggregate['id'])
@@ -157,7 +169,7 @@
@attr(type=['negative', 'gate'])
def test_aggregate_get_details_as_user(self):
# Regular user is not allowed to get aggregate details.
- aggregate_name = rand_name(self.aggregate_name_prefix)
+ aggregate_name = data_utils.rand_name(self.aggregate_name_prefix)
resp, aggregate = self.client.create_aggregate(aggregate_name)
self.addCleanup(self.client.delete_aggregate, aggregate['id'])
@@ -181,7 +193,7 @@
def test_aggregate_add_remove_host(self):
# Add an host to the given aggregate and remove.
self.useFixture(fixtures.LockFixture('availability_zone'))
- aggregate_name = rand_name(self.aggregate_name_prefix)
+ aggregate_name = data_utils.rand_name(self.aggregate_name_prefix)
resp, aggregate = self.client.create_aggregate(aggregate_name)
self.addCleanup(self.client.delete_aggregate, aggregate['id'])
@@ -203,7 +215,7 @@
def test_aggregate_add_host_list(self):
# Add an host to the given aggregate and list.
self.useFixture(fixtures.LockFixture('availability_zone'))
- aggregate_name = rand_name(self.aggregate_name_prefix)
+ aggregate_name = data_utils.rand_name(self.aggregate_name_prefix)
resp, aggregate = self.client.create_aggregate(aggregate_name)
self.addCleanup(self.client.delete_aggregate, aggregate['id'])
self.client.add_host(aggregate['id'], self.host)
@@ -221,7 +233,7 @@
def test_aggregate_add_host_get_details(self):
# Add an host to the given aggregate and get details.
self.useFixture(fixtures.LockFixture('availability_zone'))
- aggregate_name = rand_name(self.aggregate_name_prefix)
+ aggregate_name = data_utils.rand_name(self.aggregate_name_prefix)
resp, aggregate = self.client.create_aggregate(aggregate_name)
self.addCleanup(self.client.delete_aggregate, aggregate['id'])
self.client.add_host(aggregate['id'], self.host)
@@ -236,18 +248,17 @@
def test_aggregate_add_host_create_server_with_az(self):
# Add an host to the given aggregate and create a server.
self.useFixture(fixtures.LockFixture('availability_zone'))
- aggregate_name = rand_name(self.aggregate_name_prefix)
- az_name = rand_name(self.az_name_prefix)
+ aggregate_name = data_utils.rand_name(self.aggregate_name_prefix)
+ az_name = data_utils.rand_name(self.az_name_prefix)
resp, aggregate = self.client.create_aggregate(aggregate_name, az_name)
self.addCleanup(self.client.delete_aggregate, aggregate['id'])
self.client.add_host(aggregate['id'], self.host)
self.addCleanup(self.client.remove_host, aggregate['id'], self.host)
- server_name = rand_name('test_server_')
- servers_client = self.servers_client
+ server_name = data_utils.rand_name('test_server_')
admin_servers_client = self.os_adm.servers_client
- resp, server = self.create_server(name=server_name,
- availability_zone=az_name)
- servers_client.wait_for_server_status(server['id'], 'ACTIVE')
+ resp, server = self.create_test_server(name=server_name,
+ availability_zone=az_name,
+ wait_until='ACTIVE')
resp, body = admin_servers_client.get_server(server['id'])
self.assertEqual(self.host, body[self._host_key])
@@ -257,11 +268,11 @@
resp, hosts_all = self.os_adm.hosts_client.list_hosts()
hosts = map(lambda x: x['host_name'], hosts_all)
while True:
- non_exist_host = rand_name('nonexist_host_')
+ non_exist_host = data_utils.rand_name('nonexist_host_')
if non_exist_host not in hosts:
break
- aggregate_name = rand_name(self.aggregate_name_prefix)
+ aggregate_name = data_utils.rand_name(self.aggregate_name_prefix)
resp, aggregate = self.client.create_aggregate(aggregate_name)
self.addCleanup(self.client.delete_aggregate, aggregate['id'])
@@ -271,7 +282,7 @@
@attr(type=['negative', 'gate'])
def test_aggregate_add_host_as_user(self):
# Regular user is not allowed to add a host to an aggregate.
- aggregate_name = rand_name(self.aggregate_name_prefix)
+ aggregate_name = data_utils.rand_name(self.aggregate_name_prefix)
resp, aggregate = self.client.create_aggregate(aggregate_name)
self.addCleanup(self.client.delete_aggregate, aggregate['id'])
@@ -283,7 +294,7 @@
def test_aggregate_remove_host_as_user(self):
# Regular user is not allowed to remove a host from an aggregate.
self.useFixture(fixtures.LockFixture('availability_zone'))
- aggregate_name = rand_name(self.aggregate_name_prefix)
+ aggregate_name = data_utils.rand_name(self.aggregate_name_prefix)
resp, aggregate = self.client.create_aggregate(aggregate_name)
self.addCleanup(self.client.delete_aggregate, aggregate['id'])
self.client.add_host(aggregate['id'], self.host)
diff --git a/tempest/api/compute/admin/test_availability_zone.py b/tempest/api/compute/admin/test_availability_zone.py
index d1e1be6..d6488c4 100644
--- a/tempest/api/compute/admin/test_availability_zone.py
+++ b/tempest/api/compute/admin/test_availability_zone.py
@@ -20,7 +20,7 @@
from tempest.test import attr
-class AvailabilityZoneAdminTestJSON(base.BaseComputeAdminTest):
+class AvailabilityZoneAdminTestJSON(base.BaseV2ComputeAdminTest):
"""
Tests Availability Zone API List that require admin privileges
diff --git a/tempest/api/compute/admin/test_fixed_ips.py b/tempest/api/compute/admin/test_fixed_ips.py
index ee262df..427f728 100644
--- a/tempest/api/compute/admin/test_fixed_ips.py
+++ b/tempest/api/compute/admin/test_fixed_ips.py
@@ -20,7 +20,7 @@
from tempest.test import attr
-class FixedIPsTestJson(base.BaseComputeAdminTest):
+class FixedIPsTestJson(base.BaseV2ComputeAdminTest):
_interface = 'json'
@classmethod
@@ -31,7 +31,7 @@
raise cls.skipException(msg)
cls.client = cls.os_adm.fixed_ips_client
cls.non_admin_client = cls.fixed_ips_client
- resp, server = cls.create_server(wait_until='ACTIVE')
+ resp, server = cls.create_test_server(wait_until='ACTIVE')
resp, server = cls.servers_client.get_server(server['id'])
for ip_set in server['addresses']:
for ip in server['addresses'][ip_set]:
diff --git a/tempest/api/compute/admin/test_flavors.py b/tempest/api/compute/admin/test_flavors.py
index 004268e..8d2fcac 100644
--- a/tempest/api/compute/admin/test_flavors.py
+++ b/tempest/api/compute/admin/test_flavors.py
@@ -17,14 +17,13 @@
from tempest.api import compute
from tempest.api.compute import base
-from tempest.common.utils.data_utils import rand_int_id
-from tempest.common.utils.data_utils import rand_name
+from tempest.common.utils import data_utils
from tempest import exceptions
from tempest.test import attr
from tempest.test import skip_because
-class FlavorsAdminTestJSON(base.BaseComputeAdminTest):
+class FlavorsAdminTestJSON(base.BaseV2ComputeAdminTest):
"""
Tests Flavors API Create and Delete that require admin privileges
@@ -58,8 +57,8 @@
def test_create_flavor(self):
# Create a flavor and ensure it is listed
# This operation requires the user to have 'admin' role
- flavor_name = rand_name(self.flavor_name_prefix)
- new_flavor_id = rand_int_id(start=1000)
+ flavor_name = data_utils.rand_name(self.flavor_name_prefix)
+ new_flavor_id = data_utils.rand_int_id(start=1000)
# Create the flavor
resp, flavor = self.client.create_flavor(flavor_name,
@@ -80,13 +79,7 @@
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)
+ self.assertEqual(flavor['os-flavor-access:is_public'], True)
# Verify flavor is retrieved
resp, flavor = self.client.get_flavor_details(new_flavor_id)
@@ -97,8 +90,8 @@
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
- flavor_name = rand_name(self.flavor_name_prefix)
- new_flavor_id = rand_int_id(start=1000)
+ flavor_name = data_utils.rand_name(self.flavor_name_prefix)
+ new_flavor_id = data_utils.rand_int_id(start=1000)
# Create the flavor
resp, flavor = self.client.create_flavor(flavor_name,
@@ -122,8 +115,8 @@
def test_get_flavor_details_for_deleted_flavor(self):
# Delete a flavor and ensure it is not listed
# Create a test flavor
- flavor_name = rand_name(self.flavor_name_prefix)
- new_flavor_id = rand_int_id(start=1000)
+ flavor_name = data_utils.rand_name(self.flavor_name_prefix)
+ new_flavor_id = data_utils.rand_int_id(start=1000)
resp, flavor = self.client.create_flavor(flavor_name,
self.ram,
@@ -156,8 +149,16 @@
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
- flavor_name = rand_name(self.flavor_name_prefix)
- new_flavor_id = rand_int_id(start=1000)
+
+ def verify_flavor_response_extension(flavor):
+ # check some extensions for the flavor create/show/detail response
+ self.assertEqual(flavor['swap'], '')
+ self.assertEqual(int(flavor['rxtx_factor']), 1)
+ self.assertEqual(int(flavor['OS-FLV-EXT-DATA:ephemeral']), 0)
+ self.assertEqual(flavor['os-flavor-access:is_public'], True)
+
+ flavor_name = data_utils.rand_name(self.flavor_name_prefix)
+ new_flavor_id = data_utils.rand_int_id(start=1000)
# Create the flavor
resp, flavor = self.client.create_flavor(flavor_name,
@@ -171,26 +172,20 @@
self.assertEqual(flavor['vcpus'], self.vcpus)
self.assertEqual(flavor['disk'], self.disk)
self.assertEqual(int(flavor['id']), new_flavor_id)
- self.assertEqual(flavor['swap'], '')
- self.assertEqual(int(flavor['rxtx_factor']), 1)
- self.assertEqual(int(flavor['OS-FLV-EXT-DATA:ephemeral']), 0)
- if self._interface == "xml":
- XMLNS_OS_FLV_ACCESS = "http://docs.openstack.org/compute/ext/"\
- "flavor_access/api/v2"
- key = "{" + XMLNS_OS_FLV_ACCESS + "}is_public"
- self.assertEqual(flavor[key], "True")
- if self._interface == "json":
- self.assertEqual(flavor['os-flavor-access:is_public'], True)
+ verify_flavor_response_extension(flavor)
# Verify flavor is retrieved
resp, flavor = self.client.get_flavor_details(new_flavor_id)
self.assertEqual(resp.status, 200)
self.assertEqual(flavor['name'], flavor_name)
+ verify_flavor_response_extension(flavor)
+
# Check if flavor is present in list
- resp, flavors = self.client.list_flavors_with_detail()
+ resp, flavors = self.user_client.list_flavors_with_detail()
self.assertEqual(resp.status, 200)
for flavor in flavors:
if flavor['name'] == flavor_name:
+ verify_flavor_response_extension(flavor)
flag = True
self.assertTrue(flag)
@@ -200,8 +195,8 @@
# Create a flavor with os-flavor-access:is_public false should
# be present in list_details.
# This operation requires the user to have 'admin' role
- flavor_name = rand_name(self.flavor_name_prefix)
- new_flavor_id = rand_int_id(start=1000)
+ flavor_name = data_utils.rand_name(self.flavor_name_prefix)
+ new_flavor_id = data_utils.rand_int_id(start=1000)
# Create the flavor
resp, flavor = self.client.create_flavor(flavor_name,
@@ -231,8 +226,8 @@
@attr(type='gate')
def test_create_server_with_non_public_flavor(self):
# Create a flavor with os-flavor-access:is_public false
- flavor_name = rand_name(self.flavor_name_prefix)
- new_flavor_id = rand_int_id(start=1000)
+ flavor_name = data_utils.rand_name(self.flavor_name_prefix)
+ new_flavor_id = data_utils.rand_int_id(start=1000)
# Create the flavor
resp, flavor = self.client.create_flavor(flavor_name,
@@ -252,8 +247,8 @@
def test_list_public_flavor_with_other_user(self):
# Create a Flavor with public access.
# Try to List/Get flavor with another user
- flavor_name = rand_name(self.flavor_name_prefix)
- new_flavor_id = rand_int_id(start=1000)
+ flavor_name = data_utils.rand_name(self.flavor_name_prefix)
+ new_flavor_id = data_utils.rand_int_id(start=1000)
# Create the flavor
resp, flavor = self.client.create_flavor(flavor_name,
@@ -274,10 +269,10 @@
@attr(type='gate')
def test_is_public_string_variations(self):
- flavor_id_not_public = rand_int_id(start=1000)
- flavor_name_not_public = rand_name(self.flavor_name_prefix)
- flavor_id_public = rand_int_id(start=1000)
- flavor_name_public = rand_name(self.flavor_name_prefix)
+ flavor_id_not_public = data_utils.rand_int_id(start=1000)
+ flavor_name_not_public = data_utils.rand_name(self.flavor_name_prefix)
+ flavor_id_public = data_utils.rand_int_id(start=1000)
+ flavor_name_public = data_utils.rand_name(self.flavor_name_prefix)
# Create a non public flavor
resp, flavor = self.client.create_flavor(flavor_name_not_public,
@@ -317,8 +312,8 @@
@attr(type='gate')
def test_create_flavor_using_string_ram(self):
- flavor_name = rand_name(self.flavor_name_prefix)
- new_flavor_id = rand_int_id(start=1000)
+ flavor_name = data_utils.rand_name(self.flavor_name_prefix)
+ new_flavor_id = data_utils.rand_int_id(start=1000)
ram = " 1024 "
resp, flavor = self.client.create_flavor(flavor_name,
@@ -341,8 +336,8 @@
@attr(type=['negative', 'gate'])
def test_create_flavor_as_user(self):
- flavor_name = rand_name(self.flavor_name_prefix)
- new_flavor_id = rand_int_id(start=1000)
+ flavor_name = data_utils.rand_name(self.flavor_name_prefix)
+ new_flavor_id = data_utils.rand_int_id(start=1000)
self.assertRaises(exceptions.Unauthorized,
self.user_client.create_flavor,
@@ -358,8 +353,8 @@
@attr(type=['negative', 'gate'])
def test_create_flavor_using_invalid_ram(self):
- flavor_name = rand_name(self.flavor_name_prefix)
- new_flavor_id = rand_int_id(start=1000)
+ flavor_name = data_utils.rand_name(self.flavor_name_prefix)
+ new_flavor_id = data_utils.rand_int_id(start=1000)
self.assertRaises(exceptions.BadRequest,
self.client.create_flavor,
@@ -368,8 +363,8 @@
@attr(type=['negative', 'gate'])
def test_create_flavor_using_invalid_vcpus(self):
- flavor_name = rand_name(self.flavor_name_prefix)
- new_flavor_id = rand_int_id(start=1000)
+ flavor_name = data_utils.rand_name(self.flavor_name_prefix)
+ new_flavor_id = data_utils.rand_int_id(start=1000)
self.assertRaises(exceptions.BadRequest,
self.client.create_flavor,
diff --git a/tempest/api/compute/admin/test_flavors_access.py b/tempest/api/compute/admin/test_flavors_access.py
index 8213839..b866db1 100644
--- a/tempest/api/compute/admin/test_flavors_access.py
+++ b/tempest/api/compute/admin/test_flavors_access.py
@@ -15,17 +15,13 @@
# License for the specific language governing permissions and limitations
# under the License.
-import uuid
-
from tempest.api import compute
from tempest.api.compute import base
-from tempest.common.utils.data_utils import rand_int_id
-from tempest.common.utils.data_utils import rand_name
-from tempest import exceptions
+from tempest.common.utils import data_utils
from tempest.test import attr
-class FlavorsAccessTestJSON(base.BaseComputeAdminTest):
+class FlavorsAccessTestJSON(base.BaseV2ComputeAdminTest):
"""
Tests Flavor Access API extension.
@@ -43,20 +39,42 @@
cls.client = cls.os_adm.flavors_client
admin_client = cls._get_identity_admin_client()
- resp, tenants = admin_client.list_tenants()
- cls.tenant_id = [tnt['id'] for tnt in tenants if tnt['name'] ==
- cls.flavors_client.tenant_name][0]
-
+ cls.tenant = admin_client.get_tenant_by_name(cls.flavors_client.
+ tenant_name)
+ cls.tenant_id = cls.tenant['id']
+ cls.adm_tenant = admin_client.get_tenant_by_name(cls.os_adm.
+ flavors_client.
+ tenant_name)
+ cls.adm_tenant_id = cls.adm_tenant['id']
cls.flavor_name_prefix = 'test_flavor_access_'
cls.ram = 512
cls.vcpus = 1
cls.disk = 10
@attr(type='gate')
+ def test_flavor_access_list_with_private_flavor(self):
+ # Test to list flavor access successfully by querying private flavor
+ flavor_name = data_utils.rand_name(self.flavor_name_prefix)
+ new_flavor_id = data_utils.rand_int_id(start=1000)
+ resp, new_flavor = self.client.create_flavor(flavor_name,
+ self.ram, self.vcpus,
+ self.disk,
+ new_flavor_id,
+ is_public='False')
+ self.addCleanup(self.client.delete_flavor, new_flavor['id'])
+ self.assertEqual(resp.status, 200)
+ resp, flavor_access = self.client.list_flavor_access(new_flavor_id)
+ self.assertEqual(resp.status, 200)
+ self.assertEqual(len(flavor_access), 1, str(flavor_access))
+ first_flavor = flavor_access[0]
+ self.assertEqual(str(new_flavor_id), str(first_flavor['flavor_id']))
+ self.assertEqual(self.adm_tenant_id, first_flavor['tenant_id'])
+
+ @attr(type='gate')
def test_flavor_access_add_remove(self):
# Test to add and remove flavor access to a given tenant.
- flavor_name = rand_name(self.flavor_name_prefix)
- new_flavor_id = rand_int_id(start=1000)
+ flavor_name = data_utils.rand_name(self.flavor_name_prefix)
+ new_flavor_id = data_utils.rand_int_id(start=1000)
resp, new_flavor = self.client.create_flavor(flavor_name,
self.ram, self.vcpus,
self.disk,
@@ -89,84 +107,6 @@
self.assertEqual(resp.status, 200)
self.assertNotIn(new_flavor['id'], map(lambda x: x['id'], flavors))
- @attr(type=['negative', 'gate'])
- def test_flavor_non_admin_add(self):
- # Test to add flavor access as a user without admin privileges.
- flavor_name = rand_name(self.flavor_name_prefix)
- new_flavor_id = rand_int_id(start=1000)
- resp, new_flavor = self.client.create_flavor(flavor_name,
- self.ram, self.vcpus,
- self.disk,
- new_flavor_id,
- is_public='False')
- self.addCleanup(self.client.delete_flavor, new_flavor['id'])
- self.assertRaises(exceptions.Unauthorized,
- self.flavors_client.add_flavor_access,
- new_flavor['id'],
- self.tenant_id)
-
- @attr(type=['negative', 'gate'])
- def test_flavor_non_admin_remove(self):
- # Test to remove flavor access as a user without admin privileges.
- flavor_name = rand_name(self.flavor_name_prefix)
- new_flavor_id = rand_int_id(start=1000)
- resp, new_flavor = self.client.create_flavor(flavor_name,
- self.ram, self.vcpus,
- self.disk,
- new_flavor_id,
- is_public='False')
- self.addCleanup(self.client.delete_flavor, new_flavor['id'])
- # Add flavor access to a tenant.
- self.client.add_flavor_access(new_flavor['id'], self.tenant_id)
- self.addCleanup(self.client.remove_flavor_access,
- new_flavor['id'], self.tenant_id)
- self.assertRaises(exceptions.Unauthorized,
- self.flavors_client.remove_flavor_access,
- new_flavor['id'],
- self.tenant_id)
-
- @attr(type=['negative', 'gate'])
- def test_add_flavor_access_duplicate(self):
- # Create a new flavor.
- flavor_name = rand_name(self.flavor_name_prefix)
- new_flavor_id = rand_int_id(start=1000)
- resp, new_flavor = self.client.create_flavor(flavor_name,
- self.ram, self.vcpus,
- self.disk,
- new_flavor_id,
- is_public='False')
- self.addCleanup(self.client.delete_flavor, new_flavor['id'])
-
- # Add flavor access to a tenant.
- self.client.add_flavor_access(new_flavor['id'], self.tenant_id)
- self.addCleanup(self.client.remove_flavor_access,
- new_flavor['id'], self.tenant_id)
-
- # An exception should be raised when adding flavor access to the same
- # tenant
- self.assertRaises(exceptions.Duplicate,
- self.client.add_flavor_access,
- new_flavor['id'],
- self.tenant_id)
-
- @attr(type=['negative', 'gate'])
- def test_remove_flavor_access_not_found(self):
- # Create a new flavor.
- flavor_name = rand_name(self.flavor_name_prefix)
- new_flavor_id = rand_int_id(start=1000)
- resp, new_flavor = self.client.create_flavor(flavor_name,
- self.ram, self.vcpus,
- self.disk,
- new_flavor_id,
- is_public='False')
- self.addCleanup(self.client.delete_flavor, new_flavor['id'])
-
- # An exception should be raised when flavor access is not found
- self.assertRaises(exceptions.NotFound,
- self.client.remove_flavor_access,
- new_flavor['id'],
- str(uuid.uuid4()))
-
class FlavorsAdminTestXML(FlavorsAccessTestJSON):
_interface = 'xml'
diff --git a/tempest/api/compute/admin/test_flavors_access_negative.py b/tempest/api/compute/admin/test_flavors_access_negative.py
new file mode 100644
index 0000000..340c1c7
--- /dev/null
+++ b/tempest/api/compute/admin/test_flavors_access_negative.py
@@ -0,0 +1,153 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 IBM Corporation
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import uuid
+
+from tempest.api import compute
+from tempest.api.compute import base
+from tempest.common.utils import data_utils
+from tempest import exceptions
+from tempest.test import attr
+
+
+class FlavorsAccessNegativeTestJSON(base.BaseV2ComputeAdminTest):
+
+ """
+ Tests Flavor Access API extension.
+ Add and remove Flavor Access require admin privileges.
+ """
+
+ _interface = 'json'
+
+ @classmethod
+ def setUpClass(cls):
+ super(FlavorsAccessNegativeTestJSON, cls).setUpClass()
+ if not compute.FLAVOR_EXTRA_DATA_ENABLED:
+ msg = "FlavorExtraData extension not enabled."
+ raise cls.skipException(msg)
+
+ cls.client = cls.os_adm.flavors_client
+ admin_client = cls._get_identity_admin_client()
+ cls.tenant = admin_client.get_tenant_by_name(cls.flavors_client.
+ tenant_name)
+ cls.tenant_id = cls.tenant['id']
+ cls.adm_tenant = admin_client.get_tenant_by_name(cls.os_adm.
+ flavors_client.
+ tenant_name)
+ cls.adm_tenant_id = cls.adm_tenant['id']
+ cls.flavor_name_prefix = 'test_flavor_access_'
+ cls.ram = 512
+ cls.vcpus = 1
+ cls.disk = 10
+
+ @attr(type=['negative', 'gate'])
+ def test_flavor_access_list_with_public_flavor(self):
+ # Test to list flavor access with exceptions by querying public flavor
+ flavor_name = data_utils.rand_name(self.flavor_name_prefix)
+ new_flavor_id = data_utils.rand_int_id(start=1000)
+ resp, new_flavor = self.client.create_flavor(flavor_name,
+ self.ram, self.vcpus,
+ self.disk,
+ new_flavor_id,
+ is_public='True')
+ self.addCleanup(self.client.delete_flavor, new_flavor['id'])
+ self.assertEqual(resp.status, 200)
+ self.assertRaises(exceptions.NotFound,
+ self.client.list_flavor_access,
+ new_flavor_id)
+
+ @attr(type=['negative', 'gate'])
+ def test_flavor_non_admin_add(self):
+ # Test to add flavor access as a user without admin privileges.
+ flavor_name = data_utils.rand_name(self.flavor_name_prefix)
+ new_flavor_id = data_utils.rand_int_id(start=1000)
+ resp, new_flavor = self.client.create_flavor(flavor_name,
+ self.ram, self.vcpus,
+ self.disk,
+ new_flavor_id,
+ is_public='False')
+ self.addCleanup(self.client.delete_flavor, new_flavor['id'])
+ self.assertRaises(exceptions.Unauthorized,
+ self.flavors_client.add_flavor_access,
+ new_flavor['id'],
+ self.tenant_id)
+
+ @attr(type=['negative', 'gate'])
+ def test_flavor_non_admin_remove(self):
+ # Test to remove flavor access as a user without admin privileges.
+ flavor_name = data_utils.rand_name(self.flavor_name_prefix)
+ new_flavor_id = data_utils.rand_int_id(start=1000)
+ resp, new_flavor = self.client.create_flavor(flavor_name,
+ self.ram, self.vcpus,
+ self.disk,
+ new_flavor_id,
+ is_public='False')
+ self.addCleanup(self.client.delete_flavor, new_flavor['id'])
+ # Add flavor access to a tenant.
+ self.client.add_flavor_access(new_flavor['id'], self.tenant_id)
+ self.addCleanup(self.client.remove_flavor_access,
+ new_flavor['id'], self.tenant_id)
+ self.assertRaises(exceptions.Unauthorized,
+ self.flavors_client.remove_flavor_access,
+ new_flavor['id'],
+ self.tenant_id)
+
+ @attr(type=['negative', 'gate'])
+ def test_add_flavor_access_duplicate(self):
+ # Create a new flavor.
+ flavor_name = data_utils.rand_name(self.flavor_name_prefix)
+ new_flavor_id = data_utils.rand_int_id(start=1000)
+ resp, new_flavor = self.client.create_flavor(flavor_name,
+ self.ram, self.vcpus,
+ self.disk,
+ new_flavor_id,
+ is_public='False')
+ self.addCleanup(self.client.delete_flavor, new_flavor['id'])
+
+ # Add flavor access to a tenant.
+ self.client.add_flavor_access(new_flavor['id'], self.tenant_id)
+ self.addCleanup(self.client.remove_flavor_access,
+ new_flavor['id'], self.tenant_id)
+
+ # An exception should be raised when adding flavor access to the same
+ # tenant
+ self.assertRaises(exceptions.Conflict,
+ self.client.add_flavor_access,
+ new_flavor['id'],
+ self.tenant_id)
+
+ @attr(type=['negative', 'gate'])
+ def test_remove_flavor_access_not_found(self):
+ # Create a new flavor.
+ flavor_name = data_utils.rand_name(self.flavor_name_prefix)
+ new_flavor_id = data_utils.rand_int_id(start=1000)
+ resp, new_flavor = self.client.create_flavor(flavor_name,
+ self.ram, self.vcpus,
+ self.disk,
+ new_flavor_id,
+ is_public='False')
+ self.addCleanup(self.client.delete_flavor, new_flavor['id'])
+
+ # An exception should be raised when flavor access is not found
+ self.assertRaises(exceptions.NotFound,
+ self.client.remove_flavor_access,
+ new_flavor['id'],
+ str(uuid.uuid4()))
+
+
+class FlavorsAdminNegativeTestXML(FlavorsAccessNegativeTestJSON):
+ _interface = 'xml'
diff --git a/tempest/api/compute/admin/test_flavors_extra_specs.py b/tempest/api/compute/admin/test_flavors_extra_specs.py
index ce326a3..49b0429 100644
--- a/tempest/api/compute/admin/test_flavors_extra_specs.py
+++ b/tempest/api/compute/admin/test_flavors_extra_specs.py
@@ -17,17 +17,15 @@
from tempest.api import compute
from tempest.api.compute import base
-from tempest.common.utils.data_utils import rand_int_id
-from tempest.common.utils.data_utils import rand_name
-from tempest import exceptions
+from tempest.common.utils import data_utils
from tempest.test import attr
-class FlavorsExtraSpecsTestJSON(base.BaseComputeAdminTest):
+class FlavorsExtraSpecsTestJSON(base.BaseV2ComputeAdminTest):
"""
Tests Flavor Extra Spec API extension.
- SET, UNSET Flavor Extra specs require admin privileges.
+ SET, UNSET, UPDATE Flavor Extra specs require admin privileges.
GET Flavor Extra specs can be performed even by without admin privileges.
"""
@@ -41,12 +39,12 @@
raise cls.skipException(msg)
cls.client = cls.os_adm.flavors_client
- flavor_name = rand_name('test_flavor')
+ flavor_name = data_utils.rand_name('test_flavor')
ram = 512
vcpus = 1
disk = 10
ephemeral = 10
- cls.new_flavor_id = rand_int_id(start=1000)
+ cls.new_flavor_id = data_utils.rand_int_id(start=1000)
swap = 1024
rxtx = 1
# Create a flavor so as to set/get/unset extra specs
@@ -64,9 +62,9 @@
super(FlavorsExtraSpecsTestJSON, cls).tearDownClass()
@attr(type='gate')
- def test_flavor_set_get_unset_keys(self):
- # Test to SET, GET UNSET flavor extra spec as a user
- # with admin privileges.
+ def test_flavor_set_get_update_show_unset_keys(self):
+ # Test to SET, GET, UPDATE, SHOW, UNSET flavor extra
+ # spec as a user with admin privileges.
# Assigning extra specs values that are to be set
specs = {"key1": "value1", "key2": "value2"}
# SET extra specs to the flavor created in setUp
@@ -79,41 +77,55 @@
self.client.get_flavor_extra_spec(self.flavor['id'])
self.assertEqual(get_resp.status, 200)
self.assertEqual(get_body, specs)
+
+ # UPDATE the value of the extra specs key1
+ update_resp, update_body = \
+ self.client.update_flavor_extra_spec(self.flavor['id'],
+ "key1",
+ key1="value")
+ self.assertEqual(update_resp.status, 200)
+ self.assertEqual({"key1": "value"}, update_body)
+
+ # GET extra specs and verify the value of the key2
+ # is the same as before
+ get_resp, get_body = \
+ self.client.get_flavor_extra_spec(self.flavor['id'])
+ self.assertEqual(get_resp.status, 200)
+ self.assertEqual(get_body, {"key1": "value", "key2": "value2"})
+
# UNSET extra specs that were set in this test
unset_resp, _ = \
self.client.unset_flavor_extra_spec(self.flavor['id'], "key1")
self.assertEqual(unset_resp.status, 200)
-
- @attr(type=['negative', 'gate'])
- def test_flavor_non_admin_set_keys(self):
- # Test to SET flavor extra spec as a user without admin privileges.
- specs = {"key1": "value1", "key2": "value2"}
- self.assertRaises(exceptions.Unauthorized,
- self.flavors_client.set_flavor_extra_spec,
- self.flavor['id'],
- specs)
+ unset_resp, _ = \
+ self.client.unset_flavor_extra_spec(self.flavor['id'], "key2")
+ self.assertEqual(unset_resp.status, 200)
@attr(type='gate')
- def test_flavor_non_admin_get_keys(self):
+ def test_flavor_non_admin_get_all_keys(self):
specs = {"key1": "value1", "key2": "value2"}
set_resp, set_body = self.client.set_flavor_extra_spec(
self.flavor['id'], specs)
resp, body = self.flavors_client.get_flavor_extra_spec(
self.flavor['id'])
self.assertEqual(resp.status, 200)
+
for key in specs:
self.assertEqual(body[key], specs[key])
- @attr(type=['negative', 'gate'])
- def test_flavor_non_admin_unset_keys(self):
+ @attr(type='gate')
+ def test_flavor_non_admin_get_specific_key(self):
specs = {"key1": "value1", "key2": "value2"}
- set_resp, set_body = self.client.set_flavor_extra_spec(
+ resp, body = self.client.set_flavor_extra_spec(
self.flavor['id'], specs)
-
- self.assertRaises(exceptions.Unauthorized,
- self.flavors_client.unset_flavor_extra_spec,
- self.flavor['id'],
- 'key1')
+ self.assertEqual(resp.status, 200)
+ self.assertEqual(body['key1'], 'value1')
+ self.assertIn('key2', body)
+ resp, body = self.flavors_client.get_flavor_extra_spec_with_key(
+ self.flavor['id'], 'key1')
+ self.assertEqual(resp.status, 200)
+ self.assertEqual(body['key1'], 'value1')
+ self.assertNotIn('key2', body)
class FlavorsExtraSpecsTestXML(FlavorsExtraSpecsTestJSON):
diff --git a/tempest/api/compute/admin/test_flavors_extra_specs_negative.py b/tempest/api/compute/admin/test_flavors_extra_specs_negative.py
new file mode 100644
index 0000000..d7e1f9f
--- /dev/null
+++ b/tempest/api/compute/admin/test_flavors_extra_specs_negative.py
@@ -0,0 +1,136 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 OpenStack Foundation
+# All Rights Reserved.
+# Copyright 2013 IBM Corp.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.api import compute
+from tempest.api.compute import base
+from tempest.common.utils import data_utils
+from tempest import exceptions
+from tempest.test import attr
+
+
+class FlavorsExtraSpecsNegativeTestJSON(base.BaseV2ComputeAdminTest):
+
+ """
+ Negative Tests Flavor Extra Spec API extension.
+ SET, UNSET, UPDATE Flavor Extra specs require admin privileges.
+ """
+
+ _interface = 'json'
+
+ @classmethod
+ def setUpClass(cls):
+ super(FlavorsExtraSpecsNegativeTestJSON, cls).setUpClass()
+ if not compute.FLAVOR_EXTRA_DATA_ENABLED:
+ msg = "FlavorExtraData extension not enabled."
+ raise cls.skipException(msg)
+
+ cls.client = cls.os_adm.flavors_client
+ flavor_name = data_utils.rand_name('test_flavor')
+ ram = 512
+ vcpus = 1
+ disk = 10
+ ephemeral = 10
+ cls.new_flavor_id = data_utils.rand_int_id(start=1000)
+ swap = 1024
+ rxtx = 1
+ # Create a flavor
+ resp, cls.flavor = cls.client.create_flavor(flavor_name,
+ ram, vcpus,
+ disk,
+ cls.new_flavor_id,
+ ephemeral=ephemeral,
+ swap=swap, rxtx=rxtx)
+
+ @classmethod
+ def tearDownClass(cls):
+ resp, body = cls.client.delete_flavor(cls.flavor['id'])
+ cls.client.wait_for_resource_deletion(cls.flavor['id'])
+ super(FlavorsExtraSpecsNegativeTestJSON, cls).tearDownClass()
+
+ @attr(type=['negative', 'gate'])
+ def test_flavor_non_admin_set_keys(self):
+ # Test to SET flavor extra spec as a user without admin privileges.
+ specs = {"key1": "value1", "key2": "value2"}
+ self.assertRaises(exceptions.Unauthorized,
+ self.flavors_client.set_flavor_extra_spec,
+ self.flavor['id'],
+ specs)
+
+ @attr(type=['negative', 'gate'])
+ def test_flavor_non_admin_update_specific_key(self):
+ # non admin user is not allowed to update flavor extra spec
+ specs = {"key1": "value1", "key2": "value2"}
+ resp, body = self.client.set_flavor_extra_spec(
+ self.flavor['id'], specs)
+ self.assertEqual(resp.status, 200)
+ self.assertEqual(body['key1'], 'value1')
+ self.assertRaises(exceptions.Unauthorized,
+ self.flavors_client.
+ update_flavor_extra_spec,
+ self.flavor['id'],
+ 'key1',
+ key1='value1_new')
+
+ @attr(type=['negative', 'gate'])
+ def test_flavor_non_admin_unset_keys(self):
+ specs = {"key1": "value1", "key2": "value2"}
+ set_resp, set_body = self.client.set_flavor_extra_spec(
+ self.flavor['id'], specs)
+
+ self.assertRaises(exceptions.Unauthorized,
+ self.flavors_client.unset_flavor_extra_spec,
+ self.flavor['id'],
+ 'key1')
+
+ @attr(type=['negative', 'gate'])
+ def test_flavor_unset_nonexistent_key(self):
+ nonexistent_key = data_utils.rand_name('flavor_key')
+ self.assertRaises(exceptions.NotFound,
+ self.client.unset_flavor_extra_spec,
+ self.flavor['id'],
+ nonexistent_key)
+
+ @attr(type=['negative', 'gate'])
+ def test_flavor_get_nonexistent_key(self):
+ self.assertRaises(exceptions.NotFound,
+ self.flavors_client.get_flavor_extra_spec_with_key,
+ self.flavor['id'],
+ "nonexistent_key")
+
+ @attr(type=['negative', 'gate'])
+ def test_flavor_update_mismatch_key(self):
+ # the key will be updated should be match the key in the body
+ self.assertRaises(exceptions.BadRequest,
+ self.client.update_flavor_extra_spec,
+ self.flavor['id'],
+ "key2",
+ key1="value")
+
+ @attr(type=['negative', 'gate'])
+ def test_flavor_update_more_key(self):
+ # there should be just one item in the request body
+ self.assertRaises(exceptions.BadRequest,
+ self.client.update_flavor_extra_spec,
+ self.flavor['id'],
+ "key1",
+ key1="value",
+ key2="value")
+
+
+class FlavorsExtraSpecsNegativeTestXML(FlavorsExtraSpecsNegativeTestJSON):
+ _interface = 'xml'
diff --git a/tempest/api/compute/admin/test_hosts.py b/tempest/api/compute/admin/test_hosts.py
index bf09428..22e6cf1 100644
--- a/tempest/api/compute/admin/test_hosts.py
+++ b/tempest/api/compute/admin/test_hosts.py
@@ -16,12 +16,10 @@
from tempest.api.compute import base
from tempest.common import tempest_fixtures as fixtures
-from tempest.common.utils.data_utils import rand_name
-from tempest import exceptions
from tempest.test import attr
-class HostsAdminTestJSON(base.BaseComputeAdminTest):
+class HostsAdminTestJSON(base.BaseV2ComputeAdminTest):
"""
Tests hosts API using admin privileges.
@@ -33,13 +31,12 @@
def setUpClass(cls):
super(HostsAdminTestJSON, cls).setUpClass()
cls.client = cls.os_adm.hosts_client
- cls.non_admin_client = cls.os.hosts_client
@attr(type='gate')
def test_list_hosts(self):
resp, hosts = self.client.list_hosts()
self.assertEqual(200, resp.status)
- self.assertTrue(len(hosts) >= 2)
+ self.assertTrue(len(hosts) >= 2, str(hosts))
@attr(type='gate')
def test_list_hosts_with_zone(self):
@@ -53,14 +50,7 @@
self.assertTrue(len(hosts) >= 1)
self.assertIn(host, hosts)
- @attr(type='negative')
- def test_list_hosts_with_non_existent_zone(self):
- params = {'zone': 'xxx'}
- resp, hosts = self.client.list_hosts(params)
- self.assertEqual(0, len(hosts))
- self.assertEqual(200, resp.status)
-
- @attr(type='negative')
+ @attr(type='gate')
def test_list_hosts_with_a_blank_zone(self):
# If send the request with a blank zone, the request will be successful
# and it will return all the hosts list
@@ -69,34 +59,35 @@
self.assertNotEqual(0, len(hosts))
self.assertEqual(200, resp.status)
- @attr(type=['negative', 'gate'])
- def test_list_hosts_with_non_admin_user(self):
- self.assertRaises(exceptions.Unauthorized,
- self.non_admin_client.list_hosts)
+ @attr(type='gate')
+ def test_list_hosts_with_nonexistent_zone(self):
+ # If send the request with a nonexistent zone, the request will be
+ # successful and no hosts will be retured
+ params = {'zone': 'xxx'}
+ resp, hosts = self.client.list_hosts(params)
+ self.assertEqual(0, len(hosts))
+ self.assertEqual(200, resp.status)
@attr(type='gate')
def test_show_host_detail(self):
resp, hosts = self.client.list_hosts()
self.assertEqual(200, resp.status)
+
+ hosts = [host for host in hosts if host['service'] == 'compute']
self.assertTrue(len(hosts) >= 1)
- hostname = hosts[0]['host_name']
- resp, resources = self.client.show_host_detail(hostname)
- self.assertEqual(200, resp.status)
- self.assertTrue(len(resources) >= 1)
- host_resource = resources[0]['resource']
- self.assertIsNotNone(host_resource)
- self.assertIsNotNone(host_resource['cpu'])
- self.assertIsNotNone(host_resource['disk_gb'])
- self.assertIsNotNone(host_resource['memory_mb'])
- self.assertIsNotNone(host_resource['project'])
- self.assertEqual(hostname, host_resource['host'])
-
- @attr(type='negative')
- def test_show_host_detail_with_nonexist_hostname(self):
- hostname = rand_name('rand_hostname')
- self.assertRaises(exceptions.NotFound,
- self.client.show_host_detail, hostname)
+ for host in hosts:
+ hostname = host['host_name']
+ resp, resources = self.client.show_host_detail(hostname)
+ self.assertEqual(200, resp.status)
+ self.assertTrue(len(resources) >= 1)
+ host_resource = resources[0]['resource']
+ self.assertIsNotNone(host_resource)
+ self.assertIsNotNone(host_resource['cpu'])
+ self.assertIsNotNone(host_resource['disk_gb'])
+ self.assertIsNotNone(host_resource['memory_mb'])
+ self.assertIsNotNone(host_resource['project'])
+ self.assertEqual(hostname, host_resource['host'])
class HostsAdminTestXML(HostsAdminTestJSON):
diff --git a/tempest/api/compute/admin/test_hosts_negative.py b/tempest/api/compute/admin/test_hosts_negative.py
new file mode 100644
index 0000000..6b24e72
--- /dev/null
+++ b/tempest/api/compute/admin/test_hosts_negative.py
@@ -0,0 +1,174 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 Huawei Technologies Co.,LTD.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.api.compute import base
+from tempest.common.utils import data_utils
+from tempest import exceptions
+from tempest.test import attr
+
+
+class HostsAdminNegativeTestJSON(base.BaseV2ComputeAdminTest):
+
+ """
+ Tests hosts API using admin privileges.
+ """
+
+ _interface = 'json'
+
+ @classmethod
+ def setUpClass(cls):
+ super(HostsAdminNegativeTestJSON, cls).setUpClass()
+ cls.client = cls.os_adm.hosts_client
+ cls.non_admin_client = cls.os.hosts_client
+
+ def _get_host_name(self):
+ resp, hosts = self.client.list_hosts()
+ self.assertEqual(200, resp.status)
+ self.assertTrue(len(hosts) >= 1)
+ hostname = hosts[0]['host_name']
+ return hostname
+
+ @attr(type=['negative', 'gate'])
+ def test_list_hosts_with_non_admin_user(self):
+ self.assertRaises(exceptions.Unauthorized,
+ self.non_admin_client.list_hosts)
+
+ @attr(type=['negative', 'gate'])
+ def test_show_host_detail_with_nonexistent_hostname(self):
+ nonexitent_hostname = data_utils.rand_name('rand_hostname')
+ self.assertRaises(exceptions.NotFound,
+ self.client.show_host_detail, nonexitent_hostname)
+
+ @attr(type=['negative', 'gate'])
+ def test_show_host_detail_with_non_admin_user(self):
+ hostname = self._get_host_name()
+
+ self.assertRaises(exceptions.Unauthorized,
+ self.non_admin_client.show_host_detail,
+ hostname)
+
+ @attr(type=['negative', 'gate'])
+ def test_update_host_with_non_admin_user(self):
+ hostname = self._get_host_name()
+
+ self.assertRaises(exceptions.Unauthorized,
+ self.non_admin_client.update_host,
+ hostname)
+
+ @attr(type=['negative', 'gate'])
+ def test_update_host_with_extra_param(self):
+ # only 'status' and 'maintenance_mode' are the valid params.
+ hostname = self._get_host_name()
+
+ self.assertRaises(exceptions.BadRequest,
+ self.client.update_host,
+ hostname,
+ status='enable',
+ maintenance_mode='enable',
+ param='XXX')
+
+ @attr(type=['negative', 'gate'])
+ def test_update_host_with_invalid_status(self):
+ # 'status' can only be 'enable' or 'disable'
+ hostname = self._get_host_name()
+
+ self.assertRaises(exceptions.BadRequest,
+ self.client.update_host,
+ hostname,
+ status='invalid',
+ maintenance_mode='enable')
+
+ @attr(type=['negative', 'gate'])
+ def test_update_host_with_invalid_maintenance_mode(self):
+ # 'maintenance_mode' can only be 'enable' or 'disable'
+ hostname = self._get_host_name()
+
+ self.assertRaises(exceptions.BadRequest,
+ self.client.update_host,
+ hostname,
+ status='enable',
+ maintenance_mode='invalid')
+
+ @attr(type=['negative', 'gate'])
+ def test_update_host_without_param(self):
+ # 'status' or 'maintenance_mode' needed for host update
+ hostname = self._get_host_name()
+
+ self.assertRaises(exceptions.BadRequest,
+ self.client.update_host,
+ hostname)
+
+ @attr(type=['negative', 'gate'])
+ def test_update_nonexistent_host(self):
+ nonexitent_hostname = data_utils.rand_name('rand_hostname')
+
+ self.assertRaises(exceptions.NotFound,
+ self.client.update_host,
+ nonexitent_hostname,
+ status='enable',
+ maintenance_mode='enable')
+
+ @attr(type=['negative', 'gate'])
+ def test_startup_nonexistent_host(self):
+ nonexitent_hostname = data_utils.rand_name('rand_hostname')
+
+ self.assertRaises(exceptions.NotFound,
+ self.client.startup_host,
+ nonexitent_hostname)
+
+ @attr(type=['negative', 'gate'])
+ def test_startup_host_with_non_admin_user(self):
+ hostname = self._get_host_name()
+
+ self.assertRaises(exceptions.Unauthorized,
+ self.non_admin_client.startup_host,
+ hostname)
+
+ @attr(type=['negative', 'gate'])
+ def test_shutdown_nonexistent_host(self):
+ nonexitent_hostname = data_utils.rand_name('rand_hostname')
+
+ self.assertRaises(exceptions.NotFound,
+ self.client.shutdown_host,
+ nonexitent_hostname)
+
+ @attr(type=['negative', 'gate'])
+ def test_shutdown_host_with_non_admin_user(self):
+ hostname = self._get_host_name()
+
+ self.assertRaises(exceptions.Unauthorized,
+ self.non_admin_client.shutdown_host,
+ hostname)
+
+ @attr(type=['negative', 'gate'])
+ def test_reboot_nonexistent_host(self):
+ nonexitent_hostname = data_utils.rand_name('rand_hostname')
+
+ self.assertRaises(exceptions.NotFound,
+ self.client.reboot_host,
+ nonexitent_hostname)
+
+ @attr(type=['negative', 'gate'])
+ def test_reboot_host_with_non_admin_user(self):
+ hostname = self._get_host_name()
+
+ self.assertRaises(exceptions.Unauthorized,
+ self.non_admin_client.reboot_host,
+ hostname)
+
+
+class HostsAdminNegativeTestXML(HostsAdminNegativeTestJSON):
+ _interface = 'xml'
diff --git a/tempest/api/compute/admin/test_hypervisor.py b/tempest/api/compute/admin/test_hypervisor.py
index 5ca16f4..ef4f51f 100644
--- a/tempest/api/compute/admin/test_hypervisor.py
+++ b/tempest/api/compute/admin/test_hypervisor.py
@@ -16,11 +16,10 @@
# under the License.
from tempest.api.compute import base
-from tempest import exceptions
from tempest.test import attr
-class HypervisorAdminTestJSON(base.BaseComputeAdminTest):
+class HypervisorAdminTestJSON(base.BaseV2ComputeAdminTest):
"""
Tests Hypervisors API that require admin privileges
@@ -32,7 +31,6 @@
def setUpClass(cls):
super(HypervisorAdminTestJSON, cls).setUpClass()
cls.client = cls.os_adm.hypervisor_client
- cls.non_adm_client = cls.hypervisor_client
def _list_hypervisors(self):
# List of hypervisors
@@ -93,19 +91,14 @@
self.assertEqual(200, resp.status)
self.assertTrue(len(uptime) > 0)
- @attr(type=['negative', 'gate'])
- def test_get_hypervisor_list_with_non_admin_user(self):
- # List of hypervisor and available services with non admin user
- self.assertRaises(
- exceptions.Unauthorized,
- self.non_adm_client.get_hypervisor_list)
-
- @attr(type=['negative', 'gate'])
- def test_get_hypervisor_list_details_with_non_admin_user(self):
- # List of hypervisor details and available services with non admin user
- self.assertRaises(
- exceptions.Unauthorized,
- self.non_adm_client.get_hypervisor_list_details)
+ @attr(type='gate')
+ def test_search_hypervisor(self):
+ hypers = self._list_hypervisors()
+ self.assertTrue(len(hypers) > 0)
+ resp, hypers = self.client.search_hypervisor(
+ hypers[0]['hypervisor_hostname'])
+ self.assertEqual(200, resp.status)
+ self.assertTrue(len(hypers) > 0)
class HypervisorAdminTestXML(HypervisorAdminTestJSON):
diff --git a/tempest/api/compute/admin/test_hypervisor_negative.py b/tempest/api/compute/admin/test_hypervisor_negative.py
new file mode 100644
index 0000000..c6455b5
--- /dev/null
+++ b/tempest/api/compute/admin/test_hypervisor_negative.py
@@ -0,0 +1,144 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 Huawei Technologies Co.,LTD.
+# 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 uuid
+
+from tempest.api.compute import base
+from tempest.common.utils import data_utils
+from tempest import exceptions
+from tempest.test import attr
+
+
+class HypervisorAdminNegativeTestJSON(base.BaseV2ComputeAdminTest):
+
+ """
+ Tests Hypervisors API that require admin privileges
+ """
+
+ _interface = 'json'
+
+ @classmethod
+ def setUpClass(cls):
+ super(HypervisorAdminNegativeTestJSON, cls).setUpClass()
+ cls.client = cls.os_adm.hypervisor_client
+ cls.non_adm_client = cls.hypervisor_client
+
+ def _list_hypervisors(self):
+ # List of hypervisors
+ resp, hypers = self.client.get_hypervisor_list()
+ self.assertEqual(200, resp.status)
+ return hypers
+
+ @attr(type=['negative', 'gate'])
+ def test_show_nonexistent_hypervisor(self):
+ nonexistent_hypervisor_id = str(uuid.uuid4())
+
+ self.assertRaises(
+ exceptions.NotFound,
+ self.client.get_hypervisor_show_details,
+ nonexistent_hypervisor_id)
+
+ @attr(type=['negative', 'gate'])
+ def test_show_hypervisor_with_non_admin_user(self):
+ hypers = self._list_hypervisors()
+ self.assertTrue(len(hypers) > 0)
+
+ self.assertRaises(
+ exceptions.Unauthorized,
+ self.non_adm_client.get_hypervisor_show_details,
+ hypers[0]['id'])
+
+ @attr(type=['negative', 'gate'])
+ def test_show_servers_with_non_admin_user(self):
+ hypers = self._list_hypervisors()
+ self.assertTrue(len(hypers) > 0)
+
+ self.assertRaises(
+ exceptions.Unauthorized,
+ self.non_adm_client.get_hypervisor_servers,
+ hypers[0]['id'])
+
+ @attr(type=['negative', 'gate'])
+ def test_show_servers_with_nonexistent_hypervisor(self):
+ nonexistent_hypervisor_id = str(uuid.uuid4())
+
+ self.assertRaises(
+ exceptions.NotFound,
+ self.client.get_hypervisor_servers,
+ nonexistent_hypervisor_id)
+
+ @attr(type=['negative', 'gate'])
+ def test_get_hypervisor_stats_with_non_admin_user(self):
+ self.assertRaises(
+ exceptions.Unauthorized,
+ self.non_adm_client.get_hypervisor_stats)
+
+ @attr(type=['negative', 'gate'])
+ def test_get_nonexistent_hypervisor_uptime(self):
+ nonexistent_hypervisor_id = str(uuid.uuid4())
+
+ self.assertRaises(
+ exceptions.NotFound,
+ self.client.get_hypervisor_uptime,
+ nonexistent_hypervisor_id)
+
+ @attr(type=['negative', 'gate'])
+ def test_get_hypervisor_uptime_with_non_admin_user(self):
+ hypers = self._list_hypervisors()
+ self.assertTrue(len(hypers) > 0)
+
+ self.assertRaises(
+ exceptions.Unauthorized,
+ self.non_adm_client.get_hypervisor_uptime,
+ hypers[0]['id'])
+
+ @attr(type=['negative', 'gate'])
+ def test_get_hypervisor_list_with_non_admin_user(self):
+ # List of hypervisor and available services with non admin user
+ self.assertRaises(
+ exceptions.Unauthorized,
+ self.non_adm_client.get_hypervisor_list)
+
+ @attr(type=['negative', 'gate'])
+ def test_get_hypervisor_list_details_with_non_admin_user(self):
+ # List of hypervisor details and available services with non admin user
+ self.assertRaises(
+ exceptions.Unauthorized,
+ self.non_adm_client.get_hypervisor_list_details)
+
+ @attr(type=['negative', 'gate'])
+ def test_search_nonexistent_hypervisor(self):
+ nonexistent_hypervisor_name = data_utils.rand_name('test_hypervisor')
+
+ self.assertRaises(
+ exceptions.NotFound,
+ self.client.search_hypervisor,
+ nonexistent_hypervisor_name)
+
+ @attr(type=['negative', 'gate'])
+ def test_search_hypervisor_with_non_admin_user(self):
+ hypers = self._list_hypervisors()
+ self.assertTrue(len(hypers) > 0)
+
+ self.assertRaises(
+ exceptions.Unauthorized,
+ self.non_adm_client.search_hypervisor,
+ hypers[0]['hypervisor_hostname'])
+
+
+class HypervisorAdminNegativeTestXML(HypervisorAdminNegativeTestJSON):
+ _interface = 'xml'
diff --git a/tempest/api/compute/admin/test_instance_usage_audit_log.py b/tempest/api/compute/admin/test_instance_usage_audit_log.py
new file mode 100644
index 0000000..cea6e92
--- /dev/null
+++ b/tempest/api/compute/admin/test_instance_usage_audit_log.py
@@ -0,0 +1,64 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 IBM Corporation
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import datetime
+
+from tempest.api.compute import base
+from tempest.test import attr
+import urllib
+
+
+class InstanceUsageAuditLogTestJSON(base.BaseV2ComputeAdminTest):
+
+ _interface = 'json'
+
+ @classmethod
+ def setUpClass(cls):
+ super(InstanceUsageAuditLogTestJSON, cls).setUpClass()
+ cls.adm_client = cls.os_adm.instance_usages_audit_log_client
+
+ @attr(type='gate')
+ def test_list_instance_usage_audit_logs(self):
+ # list instance usage audit logs
+ resp, body = self.adm_client.list_instance_usage_audit_logs()
+ self.assertEqual(200, resp.status)
+ expected_items = ['total_errors', 'total_instances', 'log',
+ 'num_hosts_running', 'num_hosts_done',
+ 'num_hosts', 'hosts_not_run', 'overall_status',
+ 'period_ending', 'period_beginning',
+ 'num_hosts_not_run']
+ for item in expected_items:
+ self.assertIn(item, body)
+
+ @attr(type='gate')
+ def test_get_instance_usage_audit_log(self):
+ # Get instance usage audit log before specified time
+ now = datetime.datetime.now()
+ resp, body = self.adm_client.get_instance_usage_audit_log(
+ urllib.quote(now.strftime("%Y-%m-%d %H:%M:%S")))
+
+ self.assertEqual(200, resp.status)
+ expected_items = ['total_errors', 'total_instances', 'log',
+ 'num_hosts_running', 'num_hosts_done', 'num_hosts',
+ 'hosts_not_run', 'overall_status', 'period_ending',
+ 'period_beginning', 'num_hosts_not_run']
+ for item in expected_items:
+ self.assertIn(item, body)
+
+
+class InstanceUsageAuditLogTestXML(InstanceUsageAuditLogTestJSON):
+ _interface = 'xml'
diff --git a/tempest/api/compute/admin/test_instance_usage_audit_log_negative.py b/tempest/api/compute/admin/test_instance_usage_audit_log_negative.py
new file mode 100644
index 0000000..dcf41c5
--- /dev/null
+++ b/tempest/api/compute/admin/test_instance_usage_audit_log_negative.py
@@ -0,0 +1,56 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 IBM Corporation
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import datetime
+
+from tempest.api.compute import base
+from tempest import exceptions
+from tempest.test import attr
+import urllib
+
+
+class InstanceUsageAuditLogNegativeTestJSON(base.BaseV2ComputeAdminTest):
+
+ _interface = 'json'
+
+ @classmethod
+ def setUpClass(cls):
+ super(InstanceUsageAuditLogNegativeTestJSON, cls).setUpClass()
+ cls.adm_client = cls.os_adm.instance_usages_audit_log_client
+
+ @attr(type=['negative', 'gate'])
+ def test_instance_usage_audit_logs_with_nonadmin_user(self):
+ # the instance_usage_audit_logs API just can be accessed by admin user
+ self.assertRaises(exceptions.Unauthorized,
+ self.instance_usages_audit_log_client.
+ list_instance_usage_audit_logs)
+ now = datetime.datetime.now()
+ self.assertRaises(exceptions.Unauthorized,
+ self.instance_usages_audit_log_client.
+ get_instance_usage_audit_log,
+ urllib.quote(now.strftime("%Y-%m-%d %H:%M:%S")))
+
+ @attr(type=['negative', 'gate'])
+ def test_get_instance_usage_audit_logs_with_invalid_time(self):
+ self.assertRaises(exceptions.BadRequest,
+ self.adm_client.get_instance_usage_audit_log,
+ "invalid_time")
+
+
+class InstanceUsageAuditLogNegativeTestXML(
+ InstanceUsageAuditLogNegativeTestJSON):
+ _interface = 'xml'
diff --git a/tempest/api/compute/admin/test_quotas.py b/tempest/api/compute/admin/test_quotas.py
index f55f152..f49aae4 100644
--- a/tempest/api/compute/admin/test_quotas.py
+++ b/tempest/api/compute/admin/test_quotas.py
@@ -16,13 +16,16 @@
# under the License.
from tempest.api.compute import base
-from tempest.common.utils.data_utils import rand_name
+from tempest.common.utils import data_utils
+from tempest import config
from tempest import exceptions
from tempest.test import attr
+from tempest.test import skip_because
-class QuotasAdminTestJSON(base.BaseComputeAdminTest):
+class QuotasAdminTestJSON(base.BaseV2ComputeAdminTest):
_interface = 'json'
+ force_tenant_isolation = True
@classmethod
def setUpClass(cls):
@@ -33,16 +36,10 @@
cls.identity_admin_client = cls._get_identity_admin_client()
cls.sg_client = cls.security_groups_client
- resp, tenants = cls.identity_admin_client.list_tenants()
-
# NOTE(afazekas): these test cases should always create and use a new
# tenant most of them should be skipped if we can't do that
- if cls.config.compute.allow_tenant_isolation:
- cls.demo_tenant_id = cls.isolated_creds.get_primary_user().get(
- 'tenantId')
- else:
- cls.demo_tenant_id = [tnt['id'] for tnt in tenants if tnt['name']
- == cls.config.identity.tenant_name][0]
+ cls.demo_tenant_id = cls.isolated_creds.get_primary_user().get(
+ 'tenantId')
cls.default_quota_set = set(('injected_file_content_bytes',
'metadata_items', 'injected_files',
@@ -52,15 +49,6 @@
'instances', 'security_group_rules',
'cores', 'security_groups'))
- @classmethod
- def tearDownClass(cls):
- for server in cls.servers:
- try:
- cls.servers_client.delete_server(server['id'])
- except exceptions.NotFound:
- continue
- super(QuotasAdminTestJSON, cls).tearDownClass()
-
@attr(type='smoke')
def test_get_default_quotas(self):
# Admin can get the default resource quota set for a tenant
@@ -99,7 +87,7 @@
@attr(type='gate')
def test_get_updated_quotas(self):
# Verify that GET shows the updated quota set
- tenant_name = rand_name('cpu_quota_tenant_')
+ tenant_name = data_utils.rand_name('cpu_quota_tenant_')
tenant_desc = tenant_name + '-desc'
identity_client = self.os_adm.identity_client
_, tenant = identity_client.create_tenant(name=tenant_name,
@@ -129,7 +117,7 @@
self.addCleanup(self.adm_client.update_quota_set, self.demo_tenant_id,
cores=default_vcpu_quota)
- self.assertRaises(exceptions.OverLimit, self.create_server)
+ self.assertRaises(exceptions.OverLimit, self.create_test_server)
@attr(type='gate')
def test_create_server_when_memory_quota_is_full(self):
@@ -144,7 +132,7 @@
self.addCleanup(self.adm_client.update_quota_set, self.demo_tenant_id,
ram=default_mem_quota)
- self.assertRaises(exceptions.OverLimit, self.create_server)
+ self.assertRaises(exceptions.OverLimit, self.create_test_server)
@attr(type='gate')
def test_update_quota_normal_user(self):
@@ -165,8 +153,10 @@
instances=instances_quota)
self.addCleanup(self.adm_client.update_quota_set, self.demo_tenant_id,
instances=default_instances_quota)
- self.assertRaises(exceptions.OverLimit, self.create_server)
+ self.assertRaises(exceptions.OverLimit, self.create_test_server)
+ @skip_because(bug="1186354",
+ condition=config.TempestConfig().service_available.neutron)
@attr(type=['negative', 'gate'])
def test_security_groups_exceed_limit(self):
# Negative test: Creation Security Groups over limit should FAIL
@@ -189,6 +179,8 @@
self.sg_client.create_security_group,
"sg-overlimit", "sg-desc")
+ @skip_because(bug="1186354",
+ condition=config.TempestConfig().service_available.neutron)
@attr(type=['negative', 'gate'])
def test_security_groups_rules_exceed_limit(self):
# Negative test: Creation of Security Group Rules should FAIL
@@ -208,8 +200,8 @@
self.demo_tenant_id,
security_group_rules=default_sg_rules_quota)
- s_name = rand_name('securitygroup-')
- s_description = rand_name('description-')
+ s_name = data_utils.rand_name('securitygroup-')
+ s_description = data_utils.rand_name('description-')
resp, securitygroup =\
self.sg_client.create_security_group(s_name, s_description)
self.addCleanup(self.sg_client.delete_security_group,
diff --git a/tempest/api/compute/admin/test_servers.py b/tempest/api/compute/admin/test_servers.py
index 2029a2e..a3bccc3 100644
--- a/tempest/api/compute/admin/test_servers.py
+++ b/tempest/api/compute/admin/test_servers.py
@@ -15,13 +15,13 @@
# under the License.
from tempest.api.compute import base
-from tempest.common.utils.data_utils import rand_int_id
-from tempest.common.utils.data_utils import rand_name
+from tempest.common.utils import data_utils
from tempest import exceptions
from tempest.test import attr
+from tempest.test import skip_because
-class ServersAdminTestJSON(base.BaseComputeAdminTest):
+class ServersAdminTestJSON(base.BaseV2ComputeAdminTest):
"""
Tests Servers API using admin privileges
@@ -33,33 +33,30 @@
def setUpClass(cls):
super(ServersAdminTestJSON, cls).setUpClass()
cls.client = cls.os_adm.servers_client
- cls.flavors_client = cls.os_adm.flavors_client
-
cls.non_adm_client = cls.servers_client
cls.flavors_client = cls.os_adm.flavors_client
-
cls.identity_client = cls._get_identity_admin_client()
tenant = cls.identity_client.get_tenant_by_name(
cls.client.tenant_name)
cls.tenant_id = tenant['id']
- cls.s1_name = rand_name('server')
- resp, server = cls.create_server(name=cls.s1_name,
- wait_until='ACTIVE')
+ cls.s1_name = data_utils.rand_name('server')
+ resp, server = cls.create_test_server(name=cls.s1_name,
+ wait_until='ACTIVE')
cls.s1_id = server['id']
- cls.s2_name = rand_name('server')
- resp, server = cls.create_server(name=cls.s2_name,
- wait_until='ACTIVE')
+ cls.s2_name = data_utils.rand_name('server')
+ resp, server = cls.create_test_server(name=cls.s2_name,
+ wait_until='ACTIVE')
def _get_unused_flavor_id(self):
- flavor_id = rand_int_id(start=1000)
+ flavor_id = data_utils.rand_int_id(start=1000)
while True:
try:
resp, body = self.flavors_client.get_flavor_details(flavor_id)
except exceptions.NotFound:
break
- flavor_id = rand_int_id(start=1000)
+ flavor_id = data_utils.rand_int_id(start=1000)
return flavor_id
@attr(type='gate')
@@ -85,14 +82,14 @@
@attr(type='gate')
def test_admin_delete_servers_of_others(self):
# Administrator can delete servers of others
- _, server = self.create_server()
+ _, server = self.create_test_server()
resp, _ = self.client.delete_server(server['id'])
self.assertEqual('204', resp['status'])
self.servers_client.wait_for_server_termination(server['id'])
@attr(type=['negative', 'gate'])
def test_resize_server_using_overlimit_ram(self):
- flavor_name = rand_name("flavor-")
+ flavor_name = data_utils.rand_name("flavor-")
flavor_id = self._get_unused_flavor_id()
resp, quota_set = self.quotas_client.get_default_quota_set(
self.tenant_id)
@@ -110,7 +107,7 @@
@attr(type=['negative', 'gate'])
def test_resize_server_using_overlimit_vcpus(self):
- flavor_name = rand_name("flavor-")
+ flavor_name = data_utils.rand_name("flavor-")
flavor_id = self._get_unused_flavor_id()
ram = 512
resp, quota_set = self.quotas_client.get_default_quota_set(
@@ -162,6 +159,7 @@
self.client.reset_state, '999')
@attr(type='gate')
+ @skip_because(bug="1240043")
def test_get_server_diagnostics_by_admin(self):
# Retrieve server diagnostics by admin user
resp, diagnostic = self.client.get_server_diagnostics(self.s1_id)
diff --git a/tempest/api/compute/admin/test_services.py b/tempest/api/compute/admin/test_services.py
index 434ea2f..327d8b8 100644
--- a/tempest/api/compute/admin/test_services.py
+++ b/tempest/api/compute/admin/test_services.py
@@ -21,7 +21,7 @@
from tempest.test import attr
-class ServicesAdminTestJSON(base.BaseComputeAdminTest):
+class ServicesAdminTestJSON(base.BaseV2ComputeAdminTest):
"""
Tests Services API. List and Enable/Disable require admin privileges.
diff --git a/tempest/api/compute/admin/test_simple_tenant_usage.py b/tempest/api/compute/admin/test_simple_tenant_usage.py
index ce05899..a599f06 100644
--- a/tempest/api/compute/admin/test_simple_tenant_usage.py
+++ b/tempest/api/compute/admin/test_simple_tenant_usage.py
@@ -23,7 +23,7 @@
import time
-class TenantUsagesTestJSON(base.BaseComputeAdminTest):
+class TenantUsagesTestJSON(base.BaseV2ComputeAdminTest):
_interface = 'json'
@@ -39,7 +39,7 @@
cls.client.tenant_name][0]
# Create a server in the demo tenant
- resp, server = cls.create_server(wait_until='ACTIVE')
+ resp, server = cls.create_test_server(wait_until='ACTIVE')
time.sleep(2)
now = datetime.datetime.now()
diff --git a/tempest/api/compute/base.py b/tempest/api/compute/base.py
index 0b527d9..bc44a2e 100644
--- a/tempest/api/compute/base.py
+++ b/tempest/api/compute/base.py
@@ -19,9 +19,7 @@
from tempest.api import compute
from tempest import clients
-from tempest.common import isolated_creds
-from tempest.common.utils.data_utils import parse_image_id
-from tempest.common.utils.data_utils import rand_name
+from tempest.common.utils import data_utils
from tempest import exceptions
from tempest.openstack.common import log as logging
import tempest.test
@@ -34,6 +32,7 @@
"""Base test case class for all Compute API tests."""
conclusion = compute.generic_setup_package()
+ force_tenant_isolation = False
@classmethod
def setUpClass(cls):
@@ -41,50 +40,22 @@
if not cls.config.service_available.nova:
skip_msg = ("%s skipped as nova is not available" % cls.__name__)
raise cls.skipException(skip_msg)
- cls.isolated_creds = isolated_creds.IsolatedCreds(cls.__name__)
- if cls.config.compute.allow_tenant_isolation:
- creds = cls.isolated_creds.get_primary_creds()
- username, tenant_name, password = creds
- os = clients.Manager(username=username,
- password=password,
- tenant_name=tenant_name,
- interface=cls._interface)
- else:
- os = clients.Manager(interface=cls._interface)
+ os = cls.get_client_manager()
cls.os = os
- cls.servers_client = os.servers_client
- cls.flavors_client = os.flavors_client
- cls.images_client = os.images_client
- cls.extensions_client = os.extensions_client
- cls.floating_ips_client = os.floating_ips_client
- cls.keypairs_client = os.keypairs_client
- cls.security_groups_client = os.security_groups_client
- cls.quotas_client = os.quotas_client
- cls.limits_client = os.limits_client
- cls.volumes_extensions_client = os.volumes_extensions_client
- cls.volumes_client = os.volumes_client
- cls.interfaces_client = os.interfaces_client
- cls.fixed_ips_client = os.fixed_ips_client
- cls.availability_zone_client = os.availability_zone_client
- cls.aggregates_client = os.aggregates_client
- cls.services_client = os.services_client
- cls.hypervisor_client = os.hypervisor_client
cls.build_interval = cls.config.compute.build_interval
cls.build_timeout = cls.config.compute.build_timeout
cls.ssh_user = cls.config.compute.ssh_user
- cls.image_ssh_user = cls.config.compute.image_ssh_user
- cls.image_ssh_password = cls.config.compute.image_ssh_password
cls.image_ref = cls.config.compute.image_ref
cls.image_ref_alt = cls.config.compute.image_ref_alt
cls.flavor_ref = cls.config.compute.flavor_ref
cls.flavor_ref_alt = cls.config.compute.flavor_ref_alt
+ cls.image_ssh_user = cls.config.compute.image_ssh_user
+ cls.image_ssh_password = cls.config.compute.image_ssh_password
cls.servers = []
cls.images = []
- cls.servers_client_v3_auth = os.servers_client_v3_auth
-
@classmethod
def clear_servers(cls):
for server in cls.servers:
@@ -116,13 +87,13 @@
def tearDownClass(cls):
cls.clear_images()
cls.clear_servers()
- cls.isolated_creds.clear_isolated_creds()
+ cls.clear_isolated_creds()
super(BaseComputeTest, cls).tearDownClass()
@classmethod
- def create_server(cls, **kwargs):
+ def create_test_server(cls, **kwargs):
"""Wrapper utility that returns a test server."""
- name = rand_name(cls.__name__ + "-instance")
+ name = data_utils.rand_name(cls.__name__ + "-instance")
if 'name' in kwargs:
name = kwargs.pop('name')
flavor = kwargs.get('flavor', cls.flavor_ref)
@@ -147,25 +118,6 @@
return resp, body
- @classmethod
- def create_image_from_server(cls, server_id, **kwargs):
- """Wrapper utility that returns a test server."""
- name = rand_name(cls.__name__ + "-image")
- if 'name' in kwargs:
- name = kwargs.pop('name')
-
- resp, image = cls.images_client.create_image(
- server_id, name)
- image_id = parse_image_id(resp['location'])
- cls.images.append(image_id)
-
- if 'wait_until' in kwargs:
- cls.images_client.wait_for_image_status(image_id,
- kwargs['wait_until'])
- resp, image = cls.images_client.get_image(image_id)
-
- return resp, image
-
def wait_for(self, condition):
"""Repeatedly calls condition() until a timeout."""
start_time = int(time.time())
@@ -182,12 +134,148 @@
time.sleep(self.build_interval)
-class BaseComputeAdminTest(BaseComputeTest):
- """Base test case class for all Compute Admin API tests."""
+class BaseV2ComputeTest(BaseComputeTest):
@classmethod
def setUpClass(cls):
- super(BaseComputeAdminTest, cls).setUpClass()
+ super(BaseV2ComputeTest, cls).setUpClass()
+ cls.servers_client = cls.os.servers_client
+ cls.flavors_client = cls.os.flavors_client
+ cls.images_client = cls.os.images_client
+ cls.extensions_client = cls.os.extensions_client
+ cls.floating_ips_client = cls.os.floating_ips_client
+ cls.keypairs_client = cls.os.keypairs_client
+ cls.security_groups_client = cls.os.security_groups_client
+ cls.quotas_client = cls.os.quotas_client
+ cls.limits_client = cls.os.limits_client
+ cls.volumes_extensions_client = cls.os.volumes_extensions_client
+ cls.volumes_client = cls.os.volumes_client
+ cls.interfaces_client = cls.os.interfaces_client
+ cls.fixed_ips_client = cls.os.fixed_ips_client
+ cls.availability_zone_client = cls.os.availability_zone_client
+ cls.aggregates_client = cls.os.aggregates_client
+ cls.services_client = cls.os.services_client
+ cls.instance_usages_audit_log_client = \
+ cls.os.instance_usages_audit_log_client
+ cls.hypervisor_client = cls.os.hypervisor_client
+ cls.servers_client_v3_auth = cls.os.servers_client_v3_auth
+ cls.certificates_client = cls.os.certificates_client
+
+ @classmethod
+ def create_image_from_server(cls, server_id, **kwargs):
+ """Wrapper utility that returns an image created from the server."""
+ name = data_utils.rand_name(cls.__name__ + "-image")
+ if 'name' in kwargs:
+ name = kwargs.pop('name')
+
+ resp, image = cls.images_client.create_image(
+ server_id, name)
+ image_id = data_utils.parse_image_id(resp['location'])
+ cls.images.append(image_id)
+
+ if 'wait_until' in kwargs:
+ cls.images_client.wait_for_image_status(image_id,
+ kwargs['wait_until'])
+ resp, image = cls.images_client.get_image(image_id)
+
+ return resp, image
+
+ @classmethod
+ def rebuild_server(cls, **kwargs):
+ # Destroy an existing server and creates a new one
+ try:
+ cls.servers_client.delete_server(cls.server_id)
+ cls.servers_client.wait_for_server_termination(cls.server_id)
+ except Exception as exc:
+ LOG.exception(exc)
+ pass
+ resp, server = cls.create_test_server(wait_until='ACTIVE', **kwargs)
+ cls.server_id = server['id']
+ cls.password = server['adminPass']
+
+
+class BaseV2ComputeAdminTest(BaseV2ComputeTest):
+ """Base test case class for Compute Admin V2 API tests."""
+
+ @classmethod
+ def setUpClass(cls):
+ super(BaseV2ComputeAdminTest, cls).setUpClass()
+ admin_username = cls.config.compute_admin.username
+ admin_password = cls.config.compute_admin.password
+ admin_tenant = cls.config.compute_admin.tenant_name
+ if not (admin_username and admin_password and admin_tenant):
+ msg = ("Missing Compute Admin API credentials "
+ "in configuration.")
+ raise cls.skipException(msg)
+ if (cls.config.compute.allow_tenant_isolation or
+ cls.force_tenant_isolation is True):
+ creds = cls.isolated_creds.get_admin_creds()
+ admin_username, admin_tenant_name, admin_password = creds
+ cls.os_adm = clients.Manager(username=admin_username,
+ password=admin_password,
+ tenant_name=admin_tenant_name,
+ interface=cls._interface)
+ else:
+ cls.os_adm = clients.ComputeAdminManager(interface=cls._interface)
+
+
+class BaseV3ComputeTest(BaseComputeTest):
+
+ @classmethod
+ def setUpClass(cls):
+ super(BaseV3ComputeTest, cls).setUpClass()
+ if not cls.config.compute_feature_enabled.api_v3:
+ cls.tearDownClass()
+ skip_msg = ("%s skipped as nova v3 api is not available" %
+ cls.__name__)
+ raise cls.skipException(skip_msg)
+
+ cls.servers_client = cls.os.servers_v3_client
+ cls.images_client = cls.os.image_client
+ cls.services_client = cls.os.services_v3_client
+ cls.extensions_client = cls.os.extensions_v3_client
+ cls.availability_zone_client = cls.os.availability_zone_v3_client
+ cls.interfaces_client = cls.os.interfaces_v3_client
+
+ @classmethod
+ def create_image_from_server(cls, server_id, **kwargs):
+ """Wrapper utility that returns an image created from the server."""
+ name = data_utils.rand_name(cls.__name__ + "-image")
+ if 'name' in kwargs:
+ name = kwargs.pop('name')
+
+ resp, image = cls.servers_client.create_image(
+ server_id, name)
+ image_id = data_utils.parse_image_id(resp['location'])
+ cls.images.append(image_id)
+
+ if 'wait_until' in kwargs:
+ cls.images_client.wait_for_image_status(image_id,
+ kwargs['wait_until'])
+ resp, image = cls.images_client.get_image_meta(image_id)
+
+ return resp, image
+
+ @classmethod
+ def rebuild_server(cls, **kwargs):
+ # Destroy an existing server and creates a new one
+ try:
+ cls.servers_client.delete_server(cls.server_id)
+ cls.servers_client.wait_for_server_termination(cls.server_id)
+ except Exception as exc:
+ LOG.exception(exc)
+ pass
+ resp, server = cls.create_test_server(wait_until='ACTIVE', **kwargs)
+ cls.server_id = server['id']
+ cls.password = server['admin_password']
+
+
+class BaseV3ComputeAdminTest(BaseV3ComputeTest):
+ """Base test case class for all Compute Admin API V3 tests."""
+
+ @classmethod
+ def setUpClass(cls):
+ super(BaseV3ComputeAdminTest, cls).setUpClass()
admin_username = cls.config.compute_admin.username
admin_password = cls.config.compute_admin.password
admin_tenant = cls.config.compute_admin.tenant_name
@@ -198,9 +286,15 @@
if cls.config.compute.allow_tenant_isolation:
creds = cls.isolated_creds.get_admin_creds()
admin_username, admin_tenant_name, admin_password = creds
- cls.os_adm = clients.Manager(username=admin_username,
- password=admin_password,
- tenant_name=admin_tenant_name,
- interface=cls._interface)
+ os_adm = clients.Manager(username=admin_username,
+ password=admin_password,
+ tenant_name=admin_tenant_name,
+ interface=cls._interface)
else:
- cls.os_adm = clients.ComputeAdminManager(interface=cls._interface)
+ os_adm = clients.ComputeAdminManager(interface=cls._interface)
+
+ cls.os_adm = os_adm
+ cls.severs_admin_client = cls.os_adm.servers_v3_client
+ cls.services_admin_client = cls.os_adm.services_v3_client
+ cls.availability_zone_admin_client = \
+ cls.os_adm.availability_zone_v3_client
diff --git a/tempest/api/compute/certificates/__init__.py b/tempest/api/compute/certificates/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/api/compute/certificates/__init__.py
diff --git a/tempest/api/compute/certificates/test_certificates.py b/tempest/api/compute/certificates/test_certificates.py
new file mode 100644
index 0000000..4be1cff
--- /dev/null
+++ b/tempest/api/compute/certificates/test_certificates.py
@@ -0,0 +1,40 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 OpenStack Foundation
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.api.compute import base
+from tempest.test import attr
+
+
+class CertificatesTestJSON(base.BaseV2ComputeTest):
+ _interface = 'json'
+
+ @attr(type='gate')
+ def test_create_and_get_root_certificate(self):
+ # create certificates
+ resp, create_body = self.certificates_client.create_certificate()
+ self.assertEqual(200, resp.status)
+ self.assertIn('data', create_body)
+ self.assertIn('private_key', create_body)
+ # get the root certificate
+ resp, body = self.certificates_client.get_certificate('root')
+ self.assertEqual(200, resp.status)
+ self.assertIn('data', body)
+ self.assertIn('private_key', body)
+
+
+class CertificatesTestXML(CertificatesTestJSON):
+ _interface = 'xml'
diff --git a/tempest/api/compute/flavors/test_flavors.py b/tempest/api/compute/flavors/test_flavors.py
index cea13a0..eac98ea 100644
--- a/tempest/api/compute/flavors/test_flavors.py
+++ b/tempest/api/compute/flavors/test_flavors.py
@@ -20,7 +20,7 @@
from tempest.test import attr
-class FlavorsTestJSON(base.BaseComputeTest):
+class FlavorsTestJSON(base.BaseV2ComputeTest):
_interface = 'json'
@classmethod
diff --git a/tempest/api/compute/floating_ips/test_floating_ips_actions.py b/tempest/api/compute/floating_ips/test_floating_ips_actions.py
index b747b46..a06309a 100644
--- a/tempest/api/compute/floating_ips/test_floating_ips_actions.py
+++ b/tempest/api/compute/floating_ips/test_floating_ips_actions.py
@@ -15,13 +15,15 @@
# License for the specific language governing permissions and limitations
# under the License.
+import uuid
+
from tempest.api.compute import base
-from tempest.common.utils.data_utils import rand_name
+from tempest.common.utils import data_utils
from tempest import exceptions
from tempest.test import attr
-class FloatingIPsTestJSON(base.BaseComputeTest):
+class FloatingIPsTestJSON(base.BaseV2ComputeTest):
_interface = 'json'
server_id = None
floating_ip = None
@@ -33,9 +35,8 @@
cls.servers_client = cls.servers_client
# Server creation
- resp, server = cls.create_server(wait_until='ACTIVE')
+ resp, server = cls.create_test_server(wait_until='ACTIVE')
cls.server_id = server['id']
- resp, body = cls.servers_client.get_server(server['id'])
# Floating IP creation
resp, body = cls.client.create_floating_ip()
cls.floating_ip_id = body['id']
@@ -46,7 +47,9 @@
for i in range(len(body)):
cls.floating_ip_ids.append(body[i]['id'])
while True:
- cls.non_exist_id = rand_name('999')
+ cls.non_exist_id = data_utils.rand_int_id(start=999)
+ if cls.config.service_available.neutron:
+ cls.non_exist_id = str(uuid.uuid4())
if cls.non_exist_id not in cls.floating_ip_ids:
break
@@ -60,10 +63,10 @@
def test_allocate_floating_ip(self):
# Positive test:Allocation of a new floating IP to a project
# should be successful
+ resp, body = self.client.create_floating_ip()
+ self.assertEqual(200, resp.status)
+ floating_ip_id_allocated = body['id']
try:
- resp, body = self.client.create_floating_ip()
- self.assertEqual(200, resp.status)
- floating_ip_id_allocated = body['id']
resp, floating_ip_details = \
self.client.get_floating_ip_details(floating_ip_id_allocated)
# Checking if the details of allocated IP is in list of floating IP
diff --git a/tempest/api/compute/floating_ips/test_list_floating_ips.py b/tempest/api/compute/floating_ips/test_list_floating_ips.py
index 3c76069..6387f4e 100644
--- a/tempest/api/compute/floating_ips/test_list_floating_ips.py
+++ b/tempest/api/compute/floating_ips/test_list_floating_ips.py
@@ -15,13 +15,15 @@
# License for the specific language governing permissions and limitations
# under the License.
+import uuid
+
from tempest.api.compute import base
-from tempest.common.utils.data_utils import rand_name
+from tempest.common.utils import data_utils
from tempest import exceptions
from tempest.test import attr
-class FloatingIPDetailsTestJSON(base.BaseComputeTest):
+class FloatingIPDetailsTestJSON(base.BaseV2ComputeTest):
_interface = 'json'
@classmethod
@@ -30,7 +32,6 @@
cls.client = cls.floating_ips_client
cls.floating_ip = []
cls.floating_ip_id = []
- cls.random_number = 0
for i in range(3):
resp, body = cls.client.create_floating_ip()
cls.floating_ip.append(body)
@@ -87,7 +88,9 @@
floating_ip_id.append(body[i]['id'])
# Creating a non-existent floatingIP id
while True:
- non_exist_id = rand_name('999')
+ non_exist_id = data_utils.rand_int_id(start=999)
+ if self.config.service_available.neutron:
+ non_exist_id = str(uuid.uuid4())
if non_exist_id not in floating_ip_id:
break
self.assertRaises(exceptions.NotFound,
diff --git a/tempest/api/compute/images/test_image_metadata.py b/tempest/api/compute/images/test_image_metadata.py
index 8f19514..618abe2 100644
--- a/tempest/api/compute/images/test_image_metadata.py
+++ b/tempest/api/compute/images/test_image_metadata.py
@@ -16,12 +16,12 @@
# under the License.
from tempest.api.compute import base
-from tempest.common.utils.data_utils import rand_name
+from tempest.common.utils import data_utils
from tempest import exceptions
from tempest.test import attr
-class ImagesMetadataTestJSON(base.BaseComputeTest):
+class ImagesMetadataTestJSON(base.BaseV2ComputeTest):
_interface = 'json'
@classmethod
@@ -34,11 +34,11 @@
cls.servers_client = cls.servers_client
cls.client = cls.images_client
- resp, server = cls.create_server(wait_until='ACTIVE')
+ resp, server = cls.create_test_server(wait_until='ACTIVE')
cls.server_id = server['id']
# Snapshot the server once to save time
- name = rand_name('image')
+ name = data_utils.rand_name('image')
resp, _ = cls.client.create_image(cls.server_id, name, {})
cls.image_id = resp['location'].rsplit('/', 1)[1]
diff --git a/tempest/api/compute/images/test_images.py b/tempest/api/compute/images/test_images.py
index 57f26f8..4539981 100644
--- a/tempest/api/compute/images/test_images.py
+++ b/tempest/api/compute/images/test_images.py
@@ -17,13 +17,12 @@
from tempest.api import compute
from tempest.api.compute import base
from tempest import clients
-from tempest.common.utils.data_utils import parse_image_id
-from tempest.common.utils.data_utils import rand_name
+from tempest.common.utils import data_utils
from tempest import exceptions
from tempest.test import attr
-class ImagesTestJSON(base.BaseComputeTest):
+class ImagesTestJSON(base.BaseV2ComputeTest):
_interface = 'json'
@classmethod
@@ -58,7 +57,7 @@
def __create_image__(self, server_id, name, meta=None):
resp, body = self.client.create_image(server_id, name, meta)
- image_id = parse_image_id(resp['location'])
+ image_id = data_utils.parse_image_id(resp['location'])
self.client.wait_for_image_status(image_id, 'ACTIVE')
self.image_ids.append(image_id)
return resp, body
@@ -66,13 +65,13 @@
@attr(type=['negative', 'gate'])
def test_create_image_from_deleted_server(self):
# An image should not be created if the server instance is removed
- resp, server = self.create_server(wait_until='ACTIVE')
+ resp, server = self.create_test_server(wait_until='ACTIVE')
# Delete server before trying to create server
self.servers_client.delete_server(server['id'])
self.servers_client.wait_for_server_termination(server['id'])
# Create a new image after server is deleted
- name = rand_name('image')
+ name = data_utils.rand_name('image')
meta = {'image_type': 'test'}
self.assertRaises(exceptions.NotFound,
self.__create_image__,
@@ -82,7 +81,7 @@
def test_create_image_from_invalid_server(self):
# An image should not be created with invalid server id
# Create a new image with invalid server id
- name = rand_name('image')
+ name = data_utils.rand_name('image')
meta = {'image_type': 'test'}
resp = {}
resp['status'] = None
@@ -91,12 +90,12 @@
@attr(type=['negative', 'gate'])
def test_create_image_from_stopped_server(self):
- resp, server = self.create_server(wait_until='ACTIVE')
+ resp, server = self.create_test_server(wait_until='ACTIVE')
self.servers_client.stop(server['id'])
self.servers_client.wait_for_server_status(server['id'],
'SHUTOFF')
self.addCleanup(self.servers_client.delete_server, server['id'])
- snapshot_name = rand_name('test-snap-')
+ snapshot_name = data_utils.rand_name('test-snap-')
resp, image = self.create_image_from_server(server['id'],
name=snapshot_name,
wait_until='ACTIVE')
@@ -105,8 +104,8 @@
@attr(type='gate')
def test_delete_saving_image(self):
- snapshot_name = rand_name('test-snap-')
- resp, server = self.create_server(wait_until='ACTIVE')
+ snapshot_name = data_utils.rand_name('test-snap-')
+ resp, server = self.create_test_server(wait_until='ACTIVE')
self.addCleanup(self.servers_client.delete_server, server['id'])
resp, image = self.create_image_from_server(server['id'],
name=snapshot_name,
@@ -117,7 +116,7 @@
@attr(type=['negative', 'gate'])
def test_create_image_specify_uuid_35_characters_or_less(self):
# Return an error if Image ID passed is 35 characters or less
- snapshot_name = rand_name('test-snap-')
+ snapshot_name = data_utils.rand_name('test-snap-')
test_uuid = ('a' * 35)
self.assertRaises(exceptions.NotFound, self.client.create_image,
test_uuid, snapshot_name)
@@ -125,7 +124,7 @@
@attr(type=['negative', 'gate'])
def test_create_image_specify_uuid_37_characters_or_more(self):
# Return an error if Image ID passed is 37 characters or more
- snapshot_name = rand_name('test-snap-')
+ snapshot_name = data_utils.rand_name('test-snap-')
test_uuid = ('a' * 37)
self.assertRaises(exceptions.NotFound, self.client.create_image,
test_uuid, snapshot_name)
diff --git a/tempest/api/compute/images/test_images_oneserver.py b/tempest/api/compute/images/test_images_oneserver.py
index 800b2de..612c110 100644
--- a/tempest/api/compute/images/test_images_oneserver.py
+++ b/tempest/api/compute/images/test_images_oneserver.py
@@ -20,14 +20,14 @@
from tempest.api import compute
from tempest.api.compute import base
from tempest import clients
-from tempest.common.utils.data_utils import parse_image_id
-from tempest.common.utils.data_utils import rand_name
-from tempest import exceptions
+from tempest.common.utils import data_utils
+from tempest.openstack.common import log as logging
from tempest.test import attr
-from tempest.test import skip_because
+
+LOG = logging.getLogger(__name__)
-class ImagesOneServerTestJSON(base.BaseComputeTest):
+class ImagesOneServerTestJSON(base.BaseV2ComputeTest):
_interface = 'json'
def tearDown(self):
@@ -37,6 +37,20 @@
self.image_ids.remove(image_id)
super(ImagesOneServerTestJSON, self).tearDown()
+ def setUp(self):
+ # NOTE(afazekas): Normally we use the same server with all test cases,
+ # but if it has an issue, we build a new one
+ super(ImagesOneServerTestJSON, self).setUp()
+ # Check if the server is in a clean state after test
+ try:
+ self.servers_client.wait_for_server_status(self.server_id,
+ 'ACTIVE')
+ except Exception as exc:
+ LOG.exception(exc)
+ # Rebuild server if cannot reach the ACTIVE state
+ # Usually it means the server had a serius accident
+ self.rebuild_server()
+
@classmethod
def setUpClass(cls):
super(ImagesOneServerTestJSON, cls).setUpClass()
@@ -46,7 +60,8 @@
raise cls.skipException(skip_msg)
try:
- resp, cls.server = cls.create_server(wait_until='ACTIVE')
+ resp, server = cls.create_test_server(wait_until='ACTIVE')
+ cls.server_id = server['id']
except Exception:
cls.tearDownClass()
raise
@@ -65,31 +80,6 @@
cls.alt_manager = clients.AltManager()
cls.alt_client = cls.alt_manager.images_client
- @skip_because(bug="1006725")
- @attr(type=['negative', 'gate'])
- def test_create_image_specify_multibyte_character_image_name(self):
- # Return an error if the image name has multi-byte characters
- snapshot_name = rand_name('\xef\xbb\xbf')
- self.assertRaises(exceptions.BadRequest,
- self.client.create_image, self.server['id'],
- snapshot_name)
-
- @attr(type=['negative', 'gate'])
- def test_create_image_specify_invalid_metadata(self):
- # Return an error when creating image with invalid metadata
- snapshot_name = rand_name('test-snap-')
- meta = {'': ''}
- self.assertRaises(exceptions.BadRequest, self.client.create_image,
- self.server['id'], snapshot_name, meta)
-
- @attr(type=['negative', 'gate'])
- def test_create_image_specify_metadata_over_limits(self):
- # Return an error when creating image with meta data over 256 chars
- snapshot_name = rand_name('test-snap-')
- meta = {'a' * 260: 'b' * 260}
- self.assertRaises(exceptions.BadRequest, self.client.create_image,
- self.server['id'], snapshot_name, meta)
-
def _get_default_flavor_disk_size(self, flavor_id):
resp, flavor = self.flavors_client.get_flavor_details(flavor_id)
return flavor['disk']
@@ -100,11 +90,11 @@
def test_create_delete_image(self):
# Create a new image
- name = rand_name('image')
+ name = data_utils.rand_name('image')
meta = {'image_type': 'test'}
- resp, body = self.client.create_image(self.server['id'], name, meta)
+ resp, body = self.client.create_image(self.server_id, name, meta)
self.assertEqual(202, resp.status)
- image_id = parse_image_id(resp['location'])
+ image_id = data_utils.parse_image_id(resp['location'])
self.client.wait_for_image_status(image_id, 'ACTIVE')
# Verify the image was created correctly
@@ -127,49 +117,6 @@
self.assertEqual('204', resp['status'])
self.client.wait_for_resource_deletion(image_id)
- @attr(type=['negative', 'gate'])
- def test_create_second_image_when_first_image_is_being_saved(self):
- # Disallow creating another image when first image is being saved
-
- # 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.assertRaises(exceptions.Duplicate, self.client.create_image,
- self.server['id'], alt_snapshot_name)
- self.client.wait_for_image_status(image_id, 'ACTIVE')
-
- @attr(type=['negative', 'gate'])
- def test_create_image_specify_name_over_256_chars(self):
- # Return an error if snapshot name over 256 characters is passed
-
- snapshot_name = rand_name('a' * 260)
- self.assertRaises(exceptions.BadRequest, self.client.create_image,
- self.server['id'], snapshot_name)
-
- @attr(type=['negative', 'gate'])
- 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 ImagesOneServerTestXML(ImagesOneServerTestJSON):
_interface = 'xml'
diff --git a/tempest/api/compute/images/test_images_oneserver_negative.py b/tempest/api/compute/images/test_images_oneserver_negative.py
new file mode 100644
index 0000000..b4e778c
--- /dev/null
+++ b/tempest/api/compute/images/test_images_oneserver_negative.py
@@ -0,0 +1,154 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 OpenStack Foundation
+# Copyright 2013 IBM Corp.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.api import compute
+from tempest.api.compute import base
+from tempest import clients
+from tempest.common.utils import data_utils
+from tempest import exceptions
+from tempest.openstack.common import log as logging
+from tempest.test import attr
+from tempest.test import skip_because
+
+LOG = logging.getLogger(__name__)
+
+
+class ImagesOneServerNegativeTestJSON(base.BaseV2ComputeTest):
+ _interface = 'json'
+
+ 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)
+ super(ImagesOneServerNegativeTestJSON, self).tearDown()
+
+ def setUp(self):
+ # NOTE(afazekas): Normally we use the same server with all test cases,
+ # but if it has an issue, we build a new one
+ super(ImagesOneServerNegativeTestJSON, self).setUp()
+ # Check if the server is in a clean state after test
+ try:
+ self.servers_client.wait_for_server_status(self.server_id,
+ 'ACTIVE')
+ except Exception as exc:
+ LOG.exception(exc)
+ # Rebuild server if cannot reach the ACTIVE state
+ # Usually it means the server had a serius accident
+ self.rebuild_server()
+
+ @classmethod
+ def setUpClass(cls):
+ super(ImagesOneServerNegativeTestJSON, cls).setUpClass()
+ cls.client = cls.images_client
+ if not cls.config.service_available.glance:
+ skip_msg = ("%s skipped as glance is not available" % cls.__name__)
+ raise cls.skipException(skip_msg)
+
+ try:
+ resp, server = cls.create_test_server(wait_until='ACTIVE')
+ cls.server_id = server['id']
+ except Exception:
+ cls.tearDownClass()
+ raise
+
+ cls.image_ids = []
+
+ if compute.MULTI_USER:
+ if cls.config.compute.allow_tenant_isolation:
+ creds = cls.isolated_creds.get_alt_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
+
+ @skip_because(bug="1006725")
+ @attr(type=['negative', 'gate'])
+ def test_create_image_specify_multibyte_character_image_name(self):
+ # Return an error if the image name has multi-byte characters
+ snapshot_name = data_utils.rand_name('\xef\xbb\xbf')
+ self.assertRaises(exceptions.BadRequest,
+ self.client.create_image, self.server_id,
+ snapshot_name)
+
+ @attr(type=['negative', 'gate'])
+ def test_create_image_specify_invalid_metadata(self):
+ # Return an error when creating image with invalid metadata
+ snapshot_name = data_utils.rand_name('test-snap-')
+ meta = {'': ''}
+ self.assertRaises(exceptions.BadRequest, self.client.create_image,
+ self.server_id, snapshot_name, meta)
+
+ @attr(type=['negative', 'gate'])
+ def test_create_image_specify_metadata_over_limits(self):
+ # Return an error when creating image with meta data over 256 chars
+ snapshot_name = data_utils.rand_name('test-snap-')
+ meta = {'a' * 260: 'b' * 260}
+ self.assertRaises(exceptions.BadRequest, self.client.create_image,
+ self.server_id, snapshot_name, meta)
+
+ @attr(type=['negative', 'gate'])
+ def test_create_second_image_when_first_image_is_being_saved(self):
+ # Disallow creating another image when first image is being saved
+
+ # Create first snapshot
+ snapshot_name = data_utils.rand_name('test-snap-')
+ resp, body = self.client.create_image(self.server_id,
+ snapshot_name)
+ self.assertEqual(202, resp.status)
+ image_id = data_utils.parse_image_id(resp['location'])
+ self.image_ids.append(image_id)
+
+ # Create second snapshot
+ alt_snapshot_name = data_utils.rand_name('test-snap-')
+ self.assertRaises(exceptions.Conflict, self.client.create_image,
+ self.server_id, alt_snapshot_name)
+ self.client.wait_for_image_status(image_id, 'ACTIVE')
+
+ @attr(type=['negative', 'gate'])
+ def test_create_image_specify_name_over_256_chars(self):
+ # Return an error if snapshot name over 256 characters is passed
+
+ snapshot_name = data_utils.rand_name('a' * 260)
+ self.assertRaises(exceptions.BadRequest, self.client.create_image,
+ self.server_id, snapshot_name)
+
+ @attr(type=['negative', 'gate'])
+ def test_delete_image_that_is_not_yet_active(self):
+ # Return an error while trying to delete an image what is creating
+
+ snapshot_name = data_utils.rand_name('test-snap-')
+ resp, body = self.client.create_image(self.server_id, snapshot_name)
+ self.assertEqual(202, resp.status)
+ image_id = data_utils.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 ImagesOneServerNegativeTestXML(ImagesOneServerNegativeTestJSON):
+ _interface = 'xml'
diff --git a/tempest/api/compute/images/test_list_image_filters.py b/tempest/api/compute/images/test_list_image_filters.py
index 8a8d5bd..1401654 100644
--- a/tempest/api/compute/images/test_list_image_filters.py
+++ b/tempest/api/compute/images/test_list_image_filters.py
@@ -16,7 +16,7 @@
# under the License.
from tempest.api.compute import base
-from tempest.common.utils.data_utils import parse_image_id
+from tempest.common.utils import data_utils
from tempest import exceptions
from tempest.openstack.common import log as logging
from tempest.test import attr
@@ -25,7 +25,7 @@
LOG = logging.getLogger(__name__)
-class ListImageFiltersTestJSON(base.BaseComputeTest):
+class ListImageFiltersTestJSON(base.BaseV2ComputeTest):
_interface = 'json'
@classmethod
@@ -38,15 +38,15 @@
cls.image_ids = []
try:
- resp, cls.server1 = cls.create_server()
- resp, cls.server2 = cls.create_server(wait_until='ACTIVE')
+ resp, cls.server1 = cls.create_test_server()
+ resp, cls.server2 = cls.create_test_server(wait_until='ACTIVE')
# NOTE(sdague) this is faster than doing the sync wait_util on both
cls.servers_client.wait_for_server_status(cls.server1['id'],
'ACTIVE')
# Create images to be used in the filter tests
resp, body = cls.create_image_from_server(cls.server1['id'])
- cls.image1_id = parse_image_id(resp['location'])
+ cls.image1_id = data_utils.parse_image_id(resp['location'])
cls.client.wait_for_image_status(cls.image1_id, 'ACTIVE')
resp, cls.image1 = cls.client.get_image(cls.image1_id)
@@ -54,12 +54,12 @@
# Performing back-to-back create image calls on a single
# server will sometimes cause failures
resp, body = cls.create_image_from_server(cls.server2['id'])
- cls.image3_id = parse_image_id(resp['location'])
+ cls.image3_id = data_utils.parse_image_id(resp['location'])
cls.client.wait_for_image_status(cls.image3_id, 'ACTIVE')
resp, cls.image3 = cls.client.get_image(cls.image3_id)
resp, body = cls.create_image_from_server(cls.server1['id'])
- cls.image2_id = parse_image_id(resp['location'])
+ cls.image2_id = data_utils.parse_image_id(resp['location'])
cls.client.wait_for_image_status(cls.image2_id, 'ACTIVE')
resp, cls.image2 = cls.client.get_image(cls.image2_id)
diff --git a/tempest/api/compute/images/test_list_images.py b/tempest/api/compute/images/test_list_images.py
index 0647f86..a6726b6 100644
--- a/tempest/api/compute/images/test_list_images.py
+++ b/tempest/api/compute/images/test_list_images.py
@@ -19,7 +19,7 @@
from tempest.test import attr
-class ListImagesTestJSON(base.BaseComputeTest):
+class ListImagesTestJSON(base.BaseV2ComputeTest):
_interface = 'json'
@classmethod
diff --git a/tempest/api/compute/keypairs/test_keypairs.py b/tempest/api/compute/keypairs/test_keypairs.py
index 807315a..475b218 100644
--- a/tempest/api/compute/keypairs/test_keypairs.py
+++ b/tempest/api/compute/keypairs/test_keypairs.py
@@ -16,12 +16,12 @@
# under the License.
from tempest.api.compute import base
-from tempest.common.utils.data_utils import rand_name
+from tempest.common.utils import data_utils
from tempest import exceptions
from tempest.test import attr
-class KeyPairsTestJSON(base.BaseComputeTest):
+class KeyPairsTestJSON(base.BaseV2ComputeTest):
_interface = 'json'
@classmethod
@@ -35,7 +35,7 @@
# Create 3 keypairs
key_list = list()
for i in range(3):
- k_name = rand_name('keypair-')
+ k_name = data_utils.rand_name('keypair-')
resp, keypair = self.client.create_keypair(k_name)
# Need to pop these keys so that our compare doesn't fail later,
# as the keypair dicts from list API doesn't have them.
@@ -66,7 +66,7 @@
@attr(type='gate')
def test_keypair_create_delete(self):
# Keypair should be created, verified and deleted
- k_name = rand_name('keypair-')
+ k_name = data_utils.rand_name('keypair-')
resp, keypair = self.client.create_keypair(k_name)
self.assertEqual(200, resp.status)
private_key = keypair['private_key']
@@ -82,7 +82,7 @@
@attr(type='gate')
def test_get_keypair_detail(self):
# Keypair should be created, Got details by name and deleted
- k_name = rand_name('keypair-')
+ k_name = data_utils.rand_name('keypair-')
resp, keypair = self.client.create_keypair(k_name)
self.addCleanup(self.client.delete_keypair, k_name)
resp, keypair_detail = self.client.get_keypair(k_name)
@@ -99,7 +99,7 @@
@attr(type='gate')
def test_keypair_create_with_pub_key(self):
# Keypair should be created with a given public key
- k_name = rand_name('keypair-')
+ k_name = data_utils.rand_name('keypair-')
pub_key = ("ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCs"
"Ne3/1ILNCqFyfYWDeTKLD6jEXC2OQHLmietMWW+/vd"
"aZq7KZEwO0jhglaFjU1mpqq4Gz5RX156sCTNM9vRbw"
@@ -123,7 +123,7 @@
@attr(type=['negative', 'gate'])
def test_keypair_create_with_invalid_pub_key(self):
# Keypair should not be created with a non RSA public key
- k_name = rand_name('keypair-')
+ k_name = data_utils.rand_name('keypair-')
pub_key = "ssh-rsa JUNK nova@ubuntu"
self.assertRaises(exceptions.BadRequest,
self.client.create_keypair, k_name, pub_key)
@@ -131,14 +131,14 @@
@attr(type=['negative', 'gate'])
def test_keypair_delete_nonexistant_key(self):
# Non-existant key deletion should throw a proper error
- k_name = rand_name("keypair-non-existant-")
+ k_name = data_utils.rand_name("keypair-non-existant-")
self.assertRaises(exceptions.NotFound, self.client.delete_keypair,
k_name)
@attr(type=['negative', 'gate'])
def test_create_keypair_with_empty_public_key(self):
# Keypair should not be created with an empty public key
- k_name = rand_name("keypair-")
+ k_name = data_utils.rand_name("keypair-")
pub_key = ' '
self.assertRaises(exceptions.BadRequest, self.client.create_keypair,
k_name, pub_key)
@@ -146,7 +146,7 @@
@attr(type=['negative', 'gate'])
def test_create_keypair_when_public_key_bits_exceeds_maximum(self):
# Keypair should not be created when public key bits are too long
- k_name = rand_name("keypair-")
+ k_name = data_utils.rand_name("keypair-")
pub_key = 'ssh-rsa ' + 'A' * 2048 + ' openstack@ubuntu'
self.assertRaises(exceptions.BadRequest, self.client.create_keypair,
k_name, pub_key)
@@ -154,11 +154,11 @@
@attr(type=['negative', 'gate'])
def test_create_keypair_with_duplicate_name(self):
# Keypairs with duplicate names should not be created
- k_name = rand_name('keypair-')
+ k_name = data_utils.rand_name('keypair-')
resp, _ = self.client.create_keypair(k_name)
self.assertEqual(200, resp.status)
# Now try the same keyname to create another key
- self.assertRaises(exceptions.Duplicate, self.client.create_keypair,
+ self.assertRaises(exceptions.Conflict, self.client.create_keypair,
k_name)
resp, _ = self.client.delete_keypair(k_name)
self.assertEqual(202, resp.status)
diff --git a/tempest/api/compute/limits/test_absolute_limits.py b/tempest/api/compute/limits/test_absolute_limits.py
index d2430df..2809244 100644
--- a/tempest/api/compute/limits/test_absolute_limits.py
+++ b/tempest/api/compute/limits/test_absolute_limits.py
@@ -20,7 +20,7 @@
from tempest.test import attr
-class AbsoluteLimitsTestJSON(base.BaseComputeTest):
+class AbsoluteLimitsTestJSON(base.BaseV2ComputeTest):
_interface = 'json'
@classmethod
diff --git a/tempest/api/compute/security_groups/test_security_group_rules.py b/tempest/api/compute/security_groups/test_security_group_rules.py
index cbc0080..2ccc3a8 100644
--- a/tempest/api/compute/security_groups/test_security_group_rules.py
+++ b/tempest/api/compute/security_groups/test_security_group_rules.py
@@ -16,28 +16,26 @@
# under the License.
from tempest.api.compute import base
-from tempest.common.utils.data_utils import rand_name
-from tempest import config
-from tempest import exceptions
+from tempest.common.utils import data_utils
from tempest.test import attr
-from tempest.test import skip_because
-class SecurityGroupRulesTestJSON(base.BaseComputeTest):
+class SecurityGroupRulesTestJSON(base.BaseV2ComputeTest):
_interface = 'json'
@classmethod
def setUpClass(cls):
super(SecurityGroupRulesTestJSON, cls).setUpClass()
cls.client = cls.security_groups_client
+ cls.neutron_available = cls.config.service_available.neutron
- @attr(type='gate')
+ @attr(type='smoke')
def test_security_group_rules_create(self):
# Positive test: Creation of Security Group rule
# should be successful
# Creating a Security Group to add rules to it
- s_name = rand_name('securitygroup-')
- s_description = rand_name('description-')
+ s_name = data_utils.rand_name('securitygroup-')
+ s_description = data_utils.rand_name('description-')
resp, securitygroup = \
self.client.create_security_group(s_name, s_description)
securitygroup_id = securitygroup['id']
@@ -54,7 +52,7 @@
self.addCleanup(self.client.delete_security_group_rule, rule['id'])
self.assertEqual(200, resp.status)
- @attr(type='gate')
+ @attr(type='smoke')
def test_security_group_rules_create_with_optional_arguments(self):
# Positive test: Creation of Security Group rule
# with optional arguments
@@ -63,15 +61,15 @@
secgroup1 = None
secgroup2 = None
# Creating a Security Group to add rules to it
- s_name = rand_name('securitygroup-')
- s_description = rand_name('description-')
+ s_name = data_utils.rand_name('securitygroup-')
+ s_description = data_utils.rand_name('description-')
resp, securitygroup = \
self.client.create_security_group(s_name, s_description)
secgroup1 = securitygroup['id']
self.addCleanup(self.client.delete_security_group, secgroup1)
# Creating a Security Group so as to assign group_id to the rule
- s_name2 = rand_name('securitygroup-')
- s_description2 = rand_name('description-')
+ s_name2 = data_utils.rand_name('securitygroup-')
+ s_description2 = data_utils.rand_name('description-')
resp, securitygroup = \
self.client.create_security_group(s_name2, s_description2)
secgroup2 = securitygroup['id']
@@ -93,115 +91,13 @@
self.addCleanup(self.client.delete_security_group_rule, rule['id'])
self.assertEqual(200, resp.status)
- @skip_because(bug="1182384",
- condition=config.TempestConfig().service_available.neutron)
- @attr(type=['negative', 'gate'])
- def test_security_group_rules_create_with_invalid_id(self):
- # Negative test: Creation of Security Group rule should FAIL
- # with invalid Parent group id
- # Adding rules to the invalid Security Group id
- parent_group_id = rand_name('999')
- ip_protocol = 'tcp'
- from_port = 22
- to_port = 22
- self.assertRaises(exceptions.NotFound,
- self.client.create_security_group_rule,
- parent_group_id, ip_protocol, from_port, to_port)
-
- @attr(type=['negative', 'gate'])
- def test_security_group_rules_create_with_invalid_ip_protocol(self):
- # Negative test: Creation of Security Group rule should FAIL
- # with invalid ip_protocol
- # Creating a Security Group to add rule to it
- s_name = rand_name('securitygroup-')
- s_description = rand_name('description-')
- resp, securitygroup = self.client.create_security_group(s_name,
- s_description)
- # Adding rules to the created Security Group
- parent_group_id = securitygroup['id']
- ip_protocol = rand_name('999')
- from_port = 22
- to_port = 22
-
- self.addCleanup(self.client.delete_security_group, securitygroup['id'])
- self.assertRaises(exceptions.BadRequest,
- self.client.create_security_group_rule,
- parent_group_id, ip_protocol, from_port, to_port)
-
- @attr(type=['negative', 'gate'])
- def test_security_group_rules_create_with_invalid_from_port(self):
- # Negative test: Creation of Security Group rule should FAIL
- # with invalid from_port
- # Creating a Security Group to add rule to it
- s_name = rand_name('securitygroup-')
- s_description = rand_name('description-')
- resp, securitygroup = self.client.create_security_group(s_name,
- s_description)
- # Adding rules to the created Security Group
- parent_group_id = securitygroup['id']
- ip_protocol = 'tcp'
- from_port = rand_name('999')
- to_port = 22
- self.addCleanup(self.client.delete_security_group, securitygroup['id'])
- self.assertRaises(exceptions.BadRequest,
- self.client.create_security_group_rule,
- parent_group_id, ip_protocol, from_port, to_port)
-
- @attr(type=['negative', 'gate'])
- def test_security_group_rules_create_with_invalid_to_port(self):
- # Negative test: Creation of Security Group rule should FAIL
- # with invalid from_port
- # Creating a Security Group to add rule to it
- s_name = rand_name('securitygroup-')
- s_description = rand_name('description-')
- resp, securitygroup = self.client.create_security_group(s_name,
- s_description)
- # Adding rules to the created Security Group
- parent_group_id = securitygroup['id']
- ip_protocol = 'tcp'
- from_port = 22
- to_port = rand_name('999')
- self.addCleanup(self.client.delete_security_group, securitygroup['id'])
- self.assertRaises(exceptions.BadRequest,
- self.client.create_security_group_rule,
- parent_group_id, ip_protocol, from_port, to_port)
-
- @attr(type=['negative', 'gate'])
- def test_security_group_rules_create_with_invalid_port_range(self):
- # Negative test: Creation of Security Group rule should FAIL
- # with invalid port range.
- # Creating a Security Group to add rule to it.
- s_name = rand_name('securitygroup-')
- s_description = rand_name('description-')
- resp, securitygroup = self.client.create_security_group(s_name,
- s_description)
- # Adding a rule to the created Security Group
- secgroup_id = securitygroup['id']
- ip_protocol = 'tcp'
- from_port = 22
- to_port = 21
- self.addCleanup(self.client.delete_security_group, securitygroup['id'])
- self.assertRaises(exceptions.BadRequest,
- self.client.create_security_group_rule,
- secgroup_id, ip_protocol, from_port, to_port)
-
- @skip_because(bug="1182384",
- condition=config.TempestConfig().service_available.neutron)
- @attr(type=['negative', 'gate'])
- def test_security_group_rules_delete_with_invalid_id(self):
- # Negative test: Deletion of Security Group rule should be FAIL
- # with invalid rule id
- self.assertRaises(exceptions.NotFound,
- self.client.delete_security_group_rule,
- rand_name('999'))
-
- @attr(type='gate')
+ @attr(type='smoke')
def test_security_group_rules_list(self):
# Positive test: Created Security Group rules should be
# in the list of all rules
# Creating a Security Group to add rules to it
- s_name = rand_name('securitygroup-')
- s_description = rand_name('description-')
+ s_name = data_utils.rand_name('securitygroup-')
+ s_description = data_utils.rand_name('description-')
resp, securitygroup = \
self.client.create_security_group(s_name, s_description)
securitygroup_id = securitygroup['id']
@@ -238,6 +134,44 @@
self.assertTrue(any([i for i in rules if i['id'] == rule1_id]))
self.assertTrue(any([i for i in rules if i['id'] == rule2_id]))
+ @attr(type='smoke')
+ def test_security_group_rules_delete_when_peer_group_deleted(self):
+ # Positive test:rule will delete when peer group deleting
+ # Creating a Security Group to add rules to it
+ s1_name = data_utils.rand_name('securitygroup1-')
+ s1_description = data_utils.rand_name('description1-')
+ resp, sg1 = \
+ self.client.create_security_group(s1_name, s1_description)
+ self.addCleanup(self.client.delete_security_group, sg1['id'])
+ self.assertEqual(200, resp.status)
+ # Creating other Security Group to access to group1
+ s2_name = data_utils.rand_name('securitygroup2-')
+ s2_description = data_utils.rand_name('description2-')
+ resp, sg2 = \
+ self.client.create_security_group(s2_name, s2_description)
+ self.assertEqual(200, resp.status)
+ sg2_id = sg2['id']
+ # Adding rules to the Group1
+ ip_protocol = 'tcp'
+ from_port = 22
+ to_port = 22
+ resp, rule = \
+ self.client.create_security_group_rule(sg1['id'],
+ ip_protocol,
+ from_port,
+ to_port,
+ group_id=sg2_id)
+
+ self.assertEqual(200, resp.status)
+ # Delete group2
+ resp, body = self.client.delete_security_group(sg2_id)
+ self.assertEqual(202, resp.status)
+ # Get rules of the Group1
+ resp, rules = \
+ self.client.list_security_group_rules(sg1['id'])
+ # The group1 has no rules because group2 has deleted
+ self.assertEqual(0, len(rules))
+
class SecurityGroupRulesTestXML(SecurityGroupRulesTestJSON):
_interface = 'xml'
diff --git a/tempest/api/compute/security_groups/test_security_group_rules_negative.py b/tempest/api/compute/security_groups/test_security_group_rules_negative.py
new file mode 100644
index 0000000..1c38268
--- /dev/null
+++ b/tempest/api/compute/security_groups/test_security_group_rules_negative.py
@@ -0,0 +1,183 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 Huawei Technologies Co.,LTD.
+# 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 testtools
+
+from tempest.api.compute import base
+from tempest.common.utils import data_utils
+from tempest import config
+from tempest import exceptions
+from tempest.test import attr
+from tempest.test import skip_because
+
+
+class SecurityGroupRulesNegativeTestJSON(base.BaseV2ComputeTest):
+ _interface = 'json'
+
+ @classmethod
+ def setUpClass(cls):
+ super(SecurityGroupRulesNegativeTestJSON, cls).setUpClass()
+ cls.client = cls.security_groups_client
+
+ @skip_because(bug="1182384",
+ condition=config.TempestConfig().service_available.neutron)
+ @attr(type=['negative', 'smoke'])
+ def test_create_security_group_rule_with_non_existent_id(self):
+ # Negative test: Creation of Security Group rule should FAIL
+ # with non existent Parent group id
+ # Adding rules to the non existent Security Group id
+ parent_group_id = data_utils.rand_int_id(start=999)
+ ip_protocol = 'tcp'
+ from_port = 22
+ to_port = 22
+ self.assertRaises(exceptions.NotFound,
+ self.client.create_security_group_rule,
+ parent_group_id, ip_protocol, from_port, to_port)
+
+ @testtools.skipIf(config.TempestConfig().service_available.neutron,
+ "Neutron not check the security_group_id")
+ @attr(type=['negative', 'smoke'])
+ def test_create_security_group_rule_with_invalid_id(self):
+ # Negative test: Creation of Security Group rule should FAIL
+ # with Parent group id which is not integer
+ # Adding rules to the non int Security Group id
+ parent_group_id = data_utils.rand_name('non_int_id')
+ ip_protocol = 'tcp'
+ from_port = 22
+ to_port = 22
+ self.assertRaises(exceptions.BadRequest,
+ self.client.create_security_group_rule,
+ parent_group_id, ip_protocol, from_port, to_port)
+
+ @attr(type=['negative', 'smoke'])
+ def test_create_security_group_rule_duplicate(self):
+ # Negative test: Create Security Group rule duplicate should fail
+ # Creating a Security Group to add rule to it
+ s_name = data_utils.rand_name('securitygroup-')
+ s_description = data_utils.rand_name('description-')
+ resp, sg = self.client.create_security_group(s_name, s_description)
+ self.assertEqual(200, resp.status)
+ # Adding rules to the created Security Group
+ parent_group_id = sg['id']
+ ip_protocol = 'tcp'
+ from_port = 22
+ to_port = 22
+
+ self.addCleanup(self.client.delete_security_group, sg['id'])
+ resp, rule = \
+ self.client.create_security_group_rule(parent_group_id,
+ ip_protocol,
+ from_port,
+ to_port)
+ self.addCleanup(self.client.delete_security_group_rule, rule['id'])
+ self.assertEqual(200, resp.status)
+ # Add the same rule to the group should fail
+ self.assertRaises(exceptions.BadRequest,
+ self.client.create_security_group_rule,
+ parent_group_id, ip_protocol, from_port, to_port)
+
+ @attr(type=['negative', 'smoke'])
+ def test_create_security_group_rule_with_invalid_ip_protocol(self):
+ # Negative test: Creation of Security Group rule should FAIL
+ # with invalid ip_protocol
+ # Creating a Security Group to add rule to it
+ s_name = data_utils.rand_name('securitygroup-')
+ s_description = data_utils.rand_name('description-')
+ resp, securitygroup = self.client.create_security_group(s_name,
+ s_description)
+ # Adding rules to the created Security Group
+ parent_group_id = securitygroup['id']
+ ip_protocol = data_utils.rand_name('999')
+ from_port = 22
+ to_port = 22
+
+ self.addCleanup(self.client.delete_security_group, securitygroup['id'])
+ self.assertRaises(exceptions.BadRequest,
+ self.client.create_security_group_rule,
+ parent_group_id, ip_protocol, from_port, to_port)
+
+ @attr(type=['negative', 'smoke'])
+ def test_create_security_group_rule_with_invalid_from_port(self):
+ # Negative test: Creation of Security Group rule should FAIL
+ # with invalid from_port
+ # Creating a Security Group to add rule to it
+ s_name = data_utils.rand_name('securitygroup-')
+ s_description = data_utils.rand_name('description-')
+ resp, securitygroup = self.client.create_security_group(s_name,
+ s_description)
+ # Adding rules to the created Security Group
+ parent_group_id = securitygroup['id']
+ ip_protocol = 'tcp'
+ from_port = data_utils.rand_int_id(start=65536)
+ to_port = 22
+ self.addCleanup(self.client.delete_security_group, securitygroup['id'])
+ self.assertRaises(exceptions.BadRequest,
+ self.client.create_security_group_rule,
+ parent_group_id, ip_protocol, from_port, to_port)
+
+ @attr(type=['negative', 'smoke'])
+ def test_create_security_group_rule_with_invalid_to_port(self):
+ # Negative test: Creation of Security Group rule should FAIL
+ # with invalid to_port
+ # Creating a Security Group to add rule to it
+ s_name = data_utils.rand_name('securitygroup-')
+ s_description = data_utils.rand_name('description-')
+ resp, securitygroup = self.client.create_security_group(s_name,
+ s_description)
+ # Adding rules to the created Security Group
+ parent_group_id = securitygroup['id']
+ ip_protocol = 'tcp'
+ from_port = 22
+ to_port = data_utils.rand_int_id(start=65536)
+ self.addCleanup(self.client.delete_security_group, securitygroup['id'])
+ self.assertRaises(exceptions.BadRequest,
+ self.client.create_security_group_rule,
+ parent_group_id, ip_protocol, from_port, to_port)
+
+ @attr(type=['negative', 'smoke'])
+ def test_create_security_group_rule_with_invalid_port_range(self):
+ # Negative test: Creation of Security Group rule should FAIL
+ # with invalid port range.
+ # Creating a Security Group to add rule to it.
+ s_name = data_utils.rand_name('securitygroup-')
+ s_description = data_utils.rand_name('description-')
+ resp, securitygroup = self.client.create_security_group(s_name,
+ s_description)
+ # Adding a rule to the created Security Group
+ secgroup_id = securitygroup['id']
+ ip_protocol = 'tcp'
+ from_port = 22
+ to_port = 21
+ self.addCleanup(self.client.delete_security_group, securitygroup['id'])
+ self.assertRaises(exceptions.BadRequest,
+ self.client.create_security_group_rule,
+ secgroup_id, ip_protocol, from_port, to_port)
+
+ @skip_because(bug="1182384",
+ condition=config.TempestConfig().service_available.neutron)
+ @attr(type=['negative', 'smoke'])
+ def test_delete_security_group_rule_with_non_existent_id(self):
+ # Negative test: Deletion of Security Group rule should be FAIL
+ # with non existent id
+ non_existent_rule_id = data_utils.rand_int_id(start=999)
+ self.assertRaises(exceptions.NotFound,
+ self.client.delete_security_group_rule,
+ non_existent_rule_id)
+
+
+class SecurityGroupRulesNegativeTestXML(SecurityGroupRulesNegativeTestJSON):
+ _interface = 'xml'
diff --git a/tempest/api/compute/security_groups/test_security_groups.py b/tempest/api/compute/security_groups/test_security_groups.py
index fba2f53..7cb96af 100644
--- a/tempest/api/compute/security_groups/test_security_groups.py
+++ b/tempest/api/compute/security_groups/test_security_groups.py
@@ -16,22 +16,24 @@
# under the License.
import testtools
+import uuid
from tempest.api.compute import base
-from tempest.common.utils.data_utils import rand_name
+from tempest.common.utils import data_utils
from tempest import config
from tempest import exceptions
from tempest.test import attr
from tempest.test import skip_because
-class SecurityGroupsTestJSON(base.BaseComputeTest):
+class SecurityGroupsTestJSON(base.BaseV2ComputeTest):
_interface = 'json'
@classmethod
def setUpClass(cls):
super(SecurityGroupsTestJSON, cls).setUpClass()
cls.client = cls.security_groups_client
+ cls.neutron_available = cls.config.service_available.neutron
def _delete_security_group(self, securitygroup_id):
resp, _ = self.client.delete_security_group(securitygroup_id)
@@ -43,8 +45,8 @@
# Create 3 Security Groups
security_group_list = list()
for i in range(3):
- s_name = rand_name('securitygroup-')
- s_description = rand_name('description-')
+ s_name = data_utils.rand_name('securitygroup-')
+ s_description = data_utils.rand_name('description-')
resp, securitygroup = \
self.client.create_security_group(s_name, s_description)
self.assertEqual(200, resp.status)
@@ -68,8 +70,8 @@
@attr(type='gate')
def test_security_group_create_delete(self):
# Security Group should be created, verified and deleted
- s_name = rand_name('securitygroup-')
- s_description = rand_name('description-')
+ s_name = data_utils.rand_name('securitygroup-')
+ s_description = data_utils.rand_name('description-')
resp, securitygroup = \
self.client.create_security_group(s_name, s_description)
self.assertIn('id', securitygroup)
@@ -87,8 +89,8 @@
@attr(type='gate')
def test_security_group_create_get_delete(self):
# Security Group should be created, fetched and deleted
- s_name = rand_name('securitygroup-')
- s_description = rand_name('description-')
+ s_name = data_utils.rand_name('securitygroup-')
+ s_description = data_utils.rand_name('description-')
resp, securitygroup = \
self.client.create_security_group(s_name, s_description)
self.addCleanup(self._delete_security_group,
@@ -108,9 +110,7 @@
"The fetched Security Group is different "
"from the created Group")
- @skip_because(bug="1182384",
- condition=config.TempestConfig().service_available.neutron)
- @attr(type=['negative', 'gate'])
+ @attr(type=['negative', 'smoke'])
def test_security_group_get_nonexistant_group(self):
# Negative test:Should not be able to GET the details
# of non-existent Security Group
@@ -120,7 +120,9 @@
security_group_id.append(body[i]['id'])
# Creating a non-existent Security Group id
while True:
- non_exist_id = rand_name('999')
+ non_exist_id = data_utils.rand_int_id(start=999)
+ if self.neutron_available:
+ non_exist_id = str(uuid.uuid4())
if non_exist_id not in security_group_id:
break
self.assertRaises(exceptions.NotFound, self.client.get_security_group,
@@ -132,7 +134,7 @@
def test_security_group_create_with_invalid_group_name(self):
# Negative test: Security Group should not be created with group name
# as an empty string/with white spaces/chars more than 255
- s_description = rand_name('description-')
+ s_description = data_utils.rand_name('description-')
# Create Security Group with empty string as group name
self.assertRaises(exceptions.BadRequest,
self.client.create_security_group, "", s_description)
@@ -152,7 +154,7 @@
def test_security_group_create_with_invalid_group_description(self):
# Negative test:Security Group should not be created with description
# as an empty string/with white spaces/chars more than 255
- s_name = rand_name('securitygroup-')
+ s_name = data_utils.rand_name('securitygroup-')
# Create Security Group with empty string as description
self.assertRaises(exceptions.BadRequest,
self.client.create_security_group, s_name, "")
@@ -171,8 +173,8 @@
def test_security_group_create_with_duplicate_name(self):
# Negative test:Security Group with duplicate name should not
# be created
- s_name = rand_name('securitygroup-')
- s_description = rand_name('description-')
+ s_name = data_utils.rand_name('securitygroup-')
+ s_description = data_utils.rand_name('description-')
resp, security_group =\
self.client.create_security_group(s_name, s_description)
self.assertEqual(200, resp.status)
@@ -198,9 +200,7 @@
self.client.delete_security_group,
default_security_group_id)
- @skip_because(bug="1182384",
- condition=config.TempestConfig().service_available.neutron)
- @attr(type=['negative', 'gate'])
+ @attr(type=['negative', 'smoke'])
def test_delete_nonexistant_security_group(self):
# Negative test:Deletion of a non-existent Security Group should Fail
security_group_id = []
@@ -209,7 +209,9 @@
security_group_id.append(body[i]['id'])
# Creating non-existent Security Group
while True:
- non_exist_id = rand_name('999')
+ non_exist_id = data_utils.rand_int_id(start=999)
+ if self.neutron_available:
+ non_exist_id = str(uuid.uuid4())
if non_exist_id not in security_group_id:
break
self.assertRaises(exceptions.NotFound,
@@ -228,19 +230,19 @@
# and not deleted if the server is active.
# Create a couple security groups that we will use
# for the server resource this test creates
- sg_name = rand_name('sg')
- sg_desc = rand_name('sg-desc')
+ sg_name = data_utils.rand_name('sg')
+ sg_desc = data_utils.rand_name('sg-desc')
resp, sg = self.client.create_security_group(sg_name, sg_desc)
sg_id = sg['id']
- sg2_name = rand_name('sg')
- sg2_desc = rand_name('sg-desc')
+ sg2_name = data_utils.rand_name('sg')
+ sg2_desc = data_utils.rand_name('sg-desc')
resp, sg2 = self.client.create_security_group(sg2_name, sg2_desc)
sg2_id = sg2['id']
# Create server and add the security group created
# above to the server we just created
- server_name = rand_name('server')
+ server_name = data_utils.rand_name('server')
resp, server = self.servers_client.create_server(server_name,
self.image_ref,
self.flavor_ref)
diff --git a/tempest/api/compute/servers/test_attach_interfaces.py b/tempest/api/compute/servers/test_attach_interfaces.py
index 9f66a6c..a177cea 100644
--- a/tempest/api/compute/servers/test_attach_interfaces.py
+++ b/tempest/api/compute/servers/test_attach_interfaces.py
@@ -19,7 +19,7 @@
import time
-class AttachInterfacesTestJSON(base.BaseComputeTest):
+class AttachInterfacesTestJSON(base.BaseV2ComputeTest):
_interface = 'json'
@classmethod
@@ -40,8 +40,7 @@
self.assertEqual(iface['fixed_ips'][0]['ip_address'], fixed_ip)
def _create_server_get_interfaces(self):
- resp, server = self.create_server()
- self.os.servers_client.wait_for_server_status(server['id'], 'ACTIVE')
+ resp, server = self.create_test_server(wait_until='ACTIVE')
resp, ifs = self.client.list_interfaces(server['id'])
resp, body = self.client.wait_for_interface_status(
server['id'], ifs[0]['port_id'], 'ACTIVE')
diff --git a/tempest/api/compute/servers/test_create_server.py b/tempest/api/compute/servers/test_create_server.py
index 31ca387..44ce405 100644
--- a/tempest/api/compute/servers/test_create_server.py
+++ b/tempest/api/compute/servers/test_create_server.py
@@ -22,13 +22,13 @@
from tempest.api import compute
from tempest.api.compute import base
-from tempest.common.utils.data_utils import rand_name
+from tempest.common.utils import data_utils
from tempest.common.utils.linux.remote_client import RemoteClient
import tempest.config
from tempest.test import attr
-class ServersTestJSON(base.BaseComputeTest):
+class ServersTestJSON(base.BaseV2ComputeTest):
_interface = 'json'
run_ssh = tempest.config.TempestConfig().compute.run_ssh
disk_config = 'AUTO'
@@ -39,17 +39,17 @@
cls.meta = {'hello': 'world'}
cls.accessIPv4 = '1.1.1.1'
cls.accessIPv6 = '0000:0000:0000:0000:0000:babe:220.12.22.2'
- cls.name = rand_name('server')
+ cls.name = data_utils.rand_name('server')
file_contents = 'This is a test file.'
personality = [{'path': '/test.txt',
'contents': base64.b64encode(file_contents)}]
cls.client = cls.servers_client
- cli_resp = cls.create_server(name=cls.name,
- meta=cls.meta,
- accessIPv4=cls.accessIPv4,
- accessIPv6=cls.accessIPv6,
- personality=personality,
- disk_config=cls.disk_config)
+ cli_resp = cls.create_test_server(name=cls.name,
+ meta=cls.meta,
+ accessIPv4=cls.accessIPv4,
+ accessIPv6=cls.accessIPv6,
+ personality=personality,
+ disk_config=cls.disk_config)
cls.resp, cls.server_initial = cli_resp
cls.password = cls.server_initial['adminPass']
cls.client.wait_for_server_status(cls.server_initial['id'], 'ACTIVE')
diff --git a/tempest/api/compute/servers/test_disk_config.py b/tempest/api/compute/servers/test_disk_config.py
index 9abb86a..5e9ee5c 100644
--- a/tempest/api/compute/servers/test_disk_config.py
+++ b/tempest/api/compute/servers/test_disk_config.py
@@ -22,7 +22,7 @@
from tempest.test import attr
-class ServerDiskConfigTestJSON(base.BaseComputeTest):
+class ServerDiskConfigTestJSON(base.BaseV2ComputeTest):
_interface = 'json'
@classmethod
@@ -32,18 +32,25 @@
raise cls.skipException(msg)
super(ServerDiskConfigTestJSON, cls).setUpClass()
cls.client = cls.os.servers_client
+ resp, server = cls.create_test_server(wait_until='ACTIVE')
+ cls.server_id = server['id']
+
+ def _update_server_with_disk_config(self, disk_config):
+ resp, server = self.client.get_server(self.server_id)
+ if disk_config != server['OS-DCF:diskConfig']:
+ resp, server = self.client.update_server(self.server_id,
+ disk_config=disk_config)
+ self.assertEqual(200, resp.status)
+ self.client.wait_for_server_status(server['id'], 'ACTIVE')
+ resp, server = self.client.get_server(server['id'])
+ self.assertEqual(disk_config, server['OS-DCF:diskConfig'])
@attr(type='gate')
def test_rebuild_server_with_manual_disk_config(self):
# A server should be rebuilt using the manual disk config option
- resp, server = self.create_server(disk_config='AUTO',
- wait_until='ACTIVE')
+ self._update_server_with_disk_config(disk_config='AUTO')
- # Verify the specified attributes are set correctly
- resp, server = self.client.get_server(server['id'])
- self.assertEqual('AUTO', server['OS-DCF:diskConfig'])
-
- resp, server = self.client.rebuild(server['id'],
+ resp, server = self.client.rebuild(self.server_id,
self.image_ref_alt,
disk_config='MANUAL')
@@ -54,20 +61,12 @@
resp, server = self.client.get_server(server['id'])
self.assertEqual('MANUAL', server['OS-DCF:diskConfig'])
- # Delete the server
- resp, body = self.client.delete_server(server['id'])
-
@attr(type='gate')
def test_rebuild_server_with_auto_disk_config(self):
# A server should be rebuilt using the auto disk config option
- resp, server = self.create_server(disk_config='MANUAL',
- wait_until='ACTIVE')
+ self._update_server_with_disk_config(disk_config='MANUAL')
- # Verify the specified attributes are set correctly
- resp, server = self.client.get_server(server['id'])
- self.assertEqual('MANUAL', server['OS-DCF:diskConfig'])
-
- resp, server = self.client.rebuild(server['id'],
+ resp, server = self.client.rebuild(self.server_id,
self.image_ref_alt,
disk_config='AUTO')
@@ -78,48 +77,60 @@
resp, server = self.client.get_server(server['id'])
self.assertEqual('AUTO', server['OS-DCF:diskConfig'])
- # Delete the server
- resp, body = self.client.delete_server(server['id'])
+ def _get_alternative_flavor(self):
+ resp, server = self.client.get_server(self.server_id)
+
+ if int(server['flavor']['id']) == self.flavor_ref:
+ return self.flavor_ref_alt
+ else:
+ return self.flavor_ref
@testtools.skipUnless(compute.RESIZE_AVAILABLE, 'Resize not available.')
@attr(type='gate')
def test_resize_server_from_manual_to_auto(self):
# A server should be resized from manual to auto disk config
- resp, server = self.create_server(disk_config='MANUAL',
- wait_until='ACTIVE')
+ self._update_server_with_disk_config(disk_config='MANUAL')
# Resize with auto option
- self.client.resize(server['id'], self.flavor_ref_alt,
- disk_config='AUTO')
- self.client.wait_for_server_status(server['id'], 'VERIFY_RESIZE')
- self.client.confirm_resize(server['id'])
- self.client.wait_for_server_status(server['id'], 'ACTIVE')
+ flavor_id = self._get_alternative_flavor()
+ self.client.resize(self.server_id, flavor_id, disk_config='AUTO')
+ self.client.wait_for_server_status(self.server_id, 'VERIFY_RESIZE')
+ self.client.confirm_resize(self.server_id)
+ self.client.wait_for_server_status(self.server_id, 'ACTIVE')
- resp, server = self.client.get_server(server['id'])
+ resp, server = self.client.get_server(self.server_id)
self.assertEqual('AUTO', server['OS-DCF:diskConfig'])
- # Delete the server
- resp, body = self.client.delete_server(server['id'])
-
@testtools.skipUnless(compute.RESIZE_AVAILABLE, 'Resize not available.')
@attr(type='gate')
def test_resize_server_from_auto_to_manual(self):
# A server should be resized from auto to manual disk config
- resp, server = self.create_server(disk_config='AUTO',
- wait_until='ACTIVE')
+ self._update_server_with_disk_config(disk_config='AUTO')
# Resize with manual option
- self.client.resize(server['id'], self.flavor_ref_alt,
- disk_config='MANUAL')
- self.client.wait_for_server_status(server['id'], 'VERIFY_RESIZE')
- self.client.confirm_resize(server['id'])
- self.client.wait_for_server_status(server['id'], 'ACTIVE')
+ flavor_id = self._get_alternative_flavor()
+ self.client.resize(self.server_id, flavor_id, disk_config='MANUAL')
+ self.client.wait_for_server_status(self.server_id, 'VERIFY_RESIZE')
+ self.client.confirm_resize(self.server_id)
+ self.client.wait_for_server_status(self.server_id, 'ACTIVE')
- resp, server = self.client.get_server(server['id'])
+ resp, server = self.client.get_server(self.server_id)
self.assertEqual('MANUAL', server['OS-DCF:diskConfig'])
- # Delete the server
- resp, body = self.client.delete_server(server['id'])
+ @attr(type='gate')
+ def test_update_server_from_auto_to_manual(self):
+ # A server should be updated from auto to manual disk config
+ self._update_server_with_disk_config(disk_config='AUTO')
+
+ # Update the disk_config attribute to manual
+ resp, server = self.client.update_server(self.server_id,
+ disk_config='MANUAL')
+ self.assertEqual(200, resp.status)
+ self.client.wait_for_server_status(server['id'], 'ACTIVE')
+
+ # Verify the disk_config attribute is set correctly
+ resp, server = self.client.get_server(server['id'])
+ self.assertEqual('MANUAL', server['OS-DCF:diskConfig'])
class ServerDiskConfigTestXML(ServerDiskConfigTestJSON):
diff --git a/tempest/api/compute/servers/test_instance_actions.py b/tempest/api/compute/servers/test_instance_actions.py
index f13e51e..5019003 100644
--- a/tempest/api/compute/servers/test_instance_actions.py
+++ b/tempest/api/compute/servers/test_instance_actions.py
@@ -20,14 +20,14 @@
from tempest.test import attr
-class InstanceActionsTestJSON(base.BaseComputeTest):
+class InstanceActionsTestJSON(base.BaseV2ComputeTest):
_interface = 'json'
@classmethod
def setUpClass(cls):
super(InstanceActionsTestJSON, cls).setUpClass()
cls.client = cls.servers_client
- resp, server = cls.create_server(wait_until='ACTIVE')
+ resp, server = cls.create_test_server(wait_until='ACTIVE')
cls.request_id = resp['x-compute-request-id']
cls.server_id = server['id']
@@ -39,7 +39,7 @@
resp, body = self.client.list_instance_actions(self.server_id)
self.assertEqual(200, resp.status)
- self.assertTrue(len(body) == 2)
+ self.assertTrue(len(body) == 2, str(body))
self.assertTrue(any([i for i in body if i['action'] == 'create']))
self.assertTrue(any([i for i in body if i['action'] == 'reboot']))
diff --git a/tempest/api/compute/servers/test_list_server_filters.py b/tempest/api/compute/servers/test_list_server_filters.py
index 8e95671..4cbf94d 100644
--- a/tempest/api/compute/servers/test_list_server_filters.py
+++ b/tempest/api/compute/servers/test_list_server_filters.py
@@ -17,14 +17,14 @@
from tempest.api.compute import base
from tempest.api import utils
-from tempest.common.utils.data_utils import rand_name
+from tempest.common.utils import data_utils
from tempest import config
from tempest import exceptions
from tempest.test import attr
from tempest.test import skip_because
-class ListServerFiltersTestJSON(base.BaseComputeTest):
+class ListServerFiltersTestJSON(base.BaseV2ComputeTest):
_interface = 'json'
@classmethod
@@ -57,23 +57,19 @@
raise RuntimeError("Image %s (image_ref_alt) was not found!" %
cls.image_ref_alt)
- cls.s1_name = rand_name(cls.__name__ + '-instance')
- resp, cls.s1 = cls.create_server(name=cls.s1_name,
- image_id=cls.image_ref,
- flavor=cls.flavor_ref,
- wait_until='ACTIVE')
+ cls.s1_name = data_utils.rand_name(cls.__name__ + '-instance')
+ resp, cls.s1 = cls.create_test_server(name=cls.s1_name,
+ wait_until='ACTIVE')
- cls.s2_name = rand_name(cls.__name__ + '-instance')
- resp, cls.s2 = cls.create_server(name=cls.s2_name,
- image_id=cls.image_ref_alt,
- flavor=cls.flavor_ref,
- wait_until='ACTIVE')
+ cls.s2_name = data_utils.rand_name(cls.__name__ + '-instance')
+ resp, cls.s2 = cls.create_test_server(name=cls.s2_name,
+ image_id=cls.image_ref_alt,
+ wait_until='ACTIVE')
- cls.s3_name = rand_name(cls.__name__ + '-instance')
- resp, cls.s3 = cls.create_server(name=cls.s3_name,
- image_id=cls.image_ref,
- flavor=cls.flavor_ref_alt,
- wait_until='ACTIVE')
+ cls.s3_name = data_utils.rand_name(cls.__name__ + '-instance')
+ resp, cls.s3 = cls.create_test_server(name=cls.s3_name,
+ flavor=cls.flavor_ref_alt,
+ wait_until='ACTIVE')
cls.fixed_network_name = cls.config.compute.fixed_network_name
diff --git a/tempest/api/compute/servers/test_list_servers_negative.py b/tempest/api/compute/servers/test_list_servers_negative.py
index 9dd2e27..521a480 100644
--- a/tempest/api/compute/servers/test_list_servers_negative.py
+++ b/tempest/api/compute/servers/test_list_servers_negative.py
@@ -17,71 +17,19 @@
import datetime
-from tempest.api import compute
from tempest.api.compute import base
-from tempest import clients
from tempest import exceptions
from tempest.test import attr
-class ListServersNegativeTestJSON(base.BaseComputeTest):
+class ListServersNegativeTestJSON(base.BaseV2ComputeTest):
_interface = 'json'
-
- @classmethod
- def _ensure_no_servers(cls, servers, username, tenant_name):
- """
- If there are servers and there is tenant isolation then a
- skipException is raised to skip the test since it requires no servers
- to already exist for the given user/tenant.
- If there are servers and there is not tenant isolation then the test
- blocks while the servers are being deleted.
- """
- if len(servers):
- if not cls.config.compute.allow_tenant_isolation:
- for srv in servers:
- cls.client.wait_for_server_termination(srv['id'],
- ignore_error=True)
- else:
- msg = ("User/tenant %(u)s/%(t)s already have "
- "existing server instances. Skipping test." %
- {'u': username, 't': tenant_name})
- raise cls.skipException(msg)
+ force_tenant_isolation = True
@classmethod
def setUpClass(cls):
super(ListServersNegativeTestJSON, cls).setUpClass()
cls.client = cls.servers_client
- cls.servers = []
-
- if compute.MULTI_USER:
- if cls.config.compute.allow_tenant_isolation:
- creds = cls.isolated_creds.get_alt_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.servers_client
-
- # Under circumstances when there is not a tenant/user
- # created for the test case, the test case checks
- # to see if there are existing servers for the
- # either the normal user/tenant or the alt user/tenant
- # and if so, the whole test is skipped. We do this
- # because we assume a baseline of no servers at the
- # start of the test instead of destroying any existing
- # servers.
- resp, body = cls.client.list_servers()
- cls._ensure_no_servers(body['servers'],
- cls.os.username,
- cls.os.tenant_name)
-
- resp, body = cls.alt_client.list_servers()
- cls._ensure_no_servers(body['servers'],
- cls.alt_manager.username,
- cls.alt_manager.tenant_name)
# The following servers are created for use
# by the test methods in this class. These
@@ -91,10 +39,10 @@
cls.deleted_fixtures = []
cls.start_time = datetime.datetime.utcnow()
for x in xrange(2):
- resp, srv = cls.create_server()
+ resp, srv = cls.create_test_server()
cls.existing_fixtures.append(srv)
- resp, srv = cls.create_server()
+ resp, srv = cls.create_test_server()
cls.client.delete_server(srv['id'])
# We ignore errors on termination because the server may
# be put into ERROR status on a quick spawn, then delete,
@@ -188,7 +136,9 @@
# changes-since returns all instances, including deleted.
num_expected = (len(self.existing_fixtures) +
len(self.deleted_fixtures))
- self.assertEqual(num_expected, len(body['servers']))
+ self.assertEqual(num_expected, len(body['servers']),
+ "Number of servers %d is wrong in %s" %
+ (num_expected, body['servers']))
@attr(type=['negative', 'gate'])
def test_list_servers_by_changes_since_invalid_date(self):
diff --git a/tempest/api/compute/servers/test_multiple_create.py b/tempest/api/compute/servers/test_multiple_create.py
index 7e4a70b..080bd1a 100644
--- a/tempest/api/compute/servers/test_multiple_create.py
+++ b/tempest/api/compute/servers/test_multiple_create.py
@@ -16,17 +16,17 @@
# under the License.
from tempest.api.compute import base
-from tempest.common.utils.data_utils import rand_name
+from tempest.common.utils import data_utils
from tempest import exceptions
from tempest.test import attr
-class MultipleCreateTestJSON(base.BaseComputeTest):
+class MultipleCreateTestJSON(base.BaseV2ComputeTest):
_interface = 'json'
_name = 'multiple-create-test'
def _generate_name(self):
- return rand_name(self._name)
+ return data_utils.rand_name(self._name)
def _create_multiple_servers(self, name=None, wait_until=None, **kwargs):
"""
@@ -34,7 +34,7 @@
created servers into the servers list to be cleaned up after all.
"""
kwargs['name'] = kwargs.get('name', self._generate_name())
- resp, body = self.create_server(**kwargs)
+ resp, body = self.create_test_server(**kwargs)
return resp, body
diff --git a/tempest/api/compute/servers/test_server_actions.py b/tempest/api/compute/servers/test_server_actions.py
index 6f646b2..5abde56 100644
--- a/tempest/api/compute/servers/test_server_actions.py
+++ b/tempest/api/compute/servers/test_server_actions.py
@@ -22,7 +22,7 @@
from tempest.api import compute
from tempest.api.compute import base
-from tempest.common.utils.data_utils import rand_name
+from tempest.common.utils import data_utils
from tempest.common.utils.linux.remote_client import RemoteClient
import tempest.config
from tempest import exceptions
@@ -30,9 +30,10 @@
from tempest.test import skip_because
-class ServerActionsTestJSON(base.BaseComputeTest):
+class ServerActionsTestJSON(base.BaseV2ComputeTest):
_interface = 'json'
- resize_available = tempest.config.TempestConfig().compute.resize_available
+ resize_available = tempest.config.TempestConfig().\
+ compute_feature_enabled.resize
run_ssh = tempest.config.TempestConfig().compute.run_ssh
def setUp(self):
@@ -44,13 +45,13 @@
self.client.wait_for_server_status(self.server_id, 'ACTIVE')
except Exception:
# Rebuild server if something happened to it during a test
- self.rebuild_servers()
+ self.rebuild_server()
@classmethod
def setUpClass(cls):
super(ServerActionsTestJSON, cls).setUpClass()
cls.client = cls.servers_client
- cls.rebuild_servers()
+ cls.rebuild_server()
@testtools.skipUnless(compute.CHANGE_PASSWORD_AVAILABLE,
'Change password not available.')
@@ -111,7 +112,7 @@
def test_rebuild_server(self):
# The server should be rebuilt using the provided image and data
meta = {'rebuild': 'server'}
- new_name = rand_name('server')
+ new_name = data_utils.rand_name('server')
file_contents = 'Test server rebuild.'
personality = [{'path': 'rebuild.txt',
'contents': base64.b64encode(file_contents)}]
@@ -197,45 +198,88 @@
required time (%s s).' % (self.server_id, self.build_timeout)
raise exceptions.TimeoutException(message)
- @attr(type=['negative', 'gate'])
- def test_resize_server_using_nonexist_flavor(self):
- flavor_id = -1
- self.assertRaises(exceptions.BadRequest,
- self.client.resize, self.server_id, flavor_id)
+ @attr(type='gate')
+ def test_create_backup(self):
+ # Positive test:create backup successfully and rotate backups correctly
+ # create the first and the second backup
+ backup1 = data_utils.rand_name('backup')
+ resp, _ = self.servers_client.create_backup(self.server_id,
+ 'daily',
+ 2,
+ backup1)
+ oldest_backup_exist = True
- @attr(type=['negative', 'gate'])
- def test_resize_server_using_null_flavor(self):
- flavor_id = ""
- self.assertRaises(exceptions.BadRequest,
- self.client.resize, self.server_id, flavor_id)
+ # the oldest one should be deleted automatically in this test
+ def _clean_oldest_backup(oldest_backup):
+ if oldest_backup_exist:
+ self.os.image_client.delete_image(oldest_backup)
- @attr(type=['negative', 'gate'])
- def test_reboot_nonexistent_server_soft(self):
- # Negative Test: The server reboot on non existent server should return
- # an error
- self.assertRaises(exceptions.NotFound, self.client.reboot, 999, 'SOFT')
+ image1_id = data_utils.parse_image_id(resp['location'])
+ self.addCleanup(_clean_oldest_backup, image1_id)
+ self.assertEqual(202, resp.status)
- @attr(type=['negative', 'gate'])
- def test_rebuild_nonexistent_server(self):
- # Negative test: The server rebuild for a non existing server
- # should not be allowed
- meta = {'rebuild': 'server'}
- new_name = rand_name('server')
- file_contents = 'Test server rebuild.'
- personality = [{'path': '/etc/rebuild.txt',
- 'contents': base64.b64encode(file_contents)}]
- self.assertRaises(exceptions.NotFound,
- self.client.rebuild,
- 999, self.image_ref_alt,
- name=new_name,
- metadata=meta,
- personality=personality,
- adminPass='rebuild')
+ backup2 = data_utils.rand_name('backup')
+ self.servers_client.wait_for_server_status(self.server_id, 'ACTIVE')
+ resp, _ = self.servers_client.create_backup(self.server_id,
+ 'daily',
+ 2,
+ backup2)
+ image2_id = data_utils.parse_image_id(resp['location'])
+ self.addCleanup(self.os.image_client.delete_image, image2_id)
+ self.assertEqual(202, resp.status)
+
+ # verify they have been created
+ properties = {
+ 'image_type': 'backup',
+ 'backup_type': "daily",
+ 'instance_uuid': self.server_id,
+ }
+ resp, image_list = self.os.image_client.image_list_detail(
+ properties,
+ sort_key='created_at',
+ sort_dir='asc')
+ self.assertEqual(200, resp.status)
+ self.assertEqual(2, len(image_list))
+ self.assertEqual((backup1, backup2),
+ (image_list[0]['name'], image_list[1]['name']))
+
+ # create the third one, due to the rotation is 2,
+ # the first one will be deleted
+ backup3 = data_utils.rand_name('backup')
+ self.servers_client.wait_for_server_status(self.server_id, 'ACTIVE')
+ resp, _ = self.servers_client.create_backup(self.server_id,
+ 'daily',
+ 2,
+ backup3)
+ image3_id = data_utils.parse_image_id(resp['location'])
+ self.addCleanup(self.os.image_client.delete_image, image3_id)
+ self.assertEqual(202, resp.status)
+ # the first back up should be deleted
+ self.os.image_client.wait_for_resource_deletion(image1_id)
+ oldest_backup_exist = False
+ resp, image_list = self.os.image_client.image_list_detail(
+ properties,
+ sort_key='created_at',
+ sort_dir='asc')
+ self.assertEqual(200, resp.status)
+ self.assertEqual(2, len(image_list))
+ self.assertEqual((backup2, backup3),
+ (image_list[0]['name'], image_list[1]['name']))
@attr(type='gate')
def test_get_console_output(self):
# Positive test:Should be able to GET the console output
# for a given server_id and number of lines
+
+ # This reboot is necessary for outputting some console log after
+ # creating a instance backup. If a instance backup, the console
+ # log file is truncated and we cannot get any console log through
+ # "console-log" API.
+ # The detail is https://bugs.launchpad.net/nova/+bug/1251920
+ resp, body = self.servers_client.reboot(self.server_id, 'HARD')
+ self.assertEqual(202, resp.status)
+ self.servers_client.wait_for_server_status(self.server_id, 'ACTIVE')
+
def get_output():
resp, output = self.servers_client.get_console_output(
self.server_id, 10)
@@ -245,14 +289,6 @@
self.assertEqual(lines, 10)
self.wait_for(get_output)
- @attr(type=['negative', 'gate'])
- def test_get_console_output_invalid_server_id(self):
- # Negative test: Should not be able to get the console output
- # for an invalid server_id
- self.assertRaises(exceptions.NotFound,
- self.servers_client.get_console_output,
- '!@#$%^&*()', 10)
-
@skip_because(bug="1014683")
@attr(type='gate')
def test_get_console_output_server_id_in_reboot_status(self):
@@ -286,13 +322,30 @@
self.assertEqual(202, resp.status)
self.client.wait_for_server_status(self.server_id, 'ACTIVE')
- @classmethod
- def rebuild_servers(cls):
- # Destroy any existing server and creates a new one
- cls.clear_servers()
- resp, server = cls.create_server(wait_until='ACTIVE')
- cls.server_id = server['id']
- cls.password = server['adminPass']
+ @attr(type='gate')
+ def test_shelve_unshelve_server(self):
+ resp, server = self.client.shelve_server(self.server_id)
+ self.assertEqual(202, resp.status)
+
+ offload_time = self.config.compute.shelved_offload_time
+ if offload_time >= 0:
+ self.client.wait_for_server_status(self.server_id,
+ 'SHELVED_OFFLOADED',
+ extra_timeout=offload_time)
+ else:
+ self.client.wait_for_server_status(self.server_id,
+ 'SHELVED')
+
+ resp, server = self.client.get_server(self.server_id)
+ image_name = server['name'] + '-shelved'
+ params = {'name': image_name}
+ resp, images = self.images_client.list_images(params)
+ self.assertEqual(1, len(images))
+ self.assertEqual(image_name, images[0]['name'])
+
+ resp, server = self.client.unshelve_server(self.server_id)
+ self.assertEqual(202, resp.status)
+ self.client.wait_for_server_status(self.server_id, 'ACTIVE')
@attr(type='gate')
def test_stop_start_server(self):
@@ -312,7 +365,7 @@
self.assertEqual(200, resp.status)
self.assertEqual(server['status'], 'ACTIVE')
# Locked server is not allowed to be stopped by non-admin user
- self.assertRaises(exceptions.BadRequest,
+ self.assertRaises(exceptions.Conflict,
self.servers_client.stop, self.server_id)
resp, server = self.servers_client.unlock_server(self.server_id)
self.assertEqual(202, resp.status)
diff --git a/tempest/api/compute/servers/test_server_addresses.py b/tempest/api/compute/servers/test_server_addresses.py
index b1b9253..7ca8a52 100644
--- a/tempest/api/compute/servers/test_server_addresses.py
+++ b/tempest/api/compute/servers/test_server_addresses.py
@@ -20,7 +20,7 @@
from tempest.test import attr
-class ServerAddressesTest(base.BaseComputeTest):
+class ServerAddressesTest(base.BaseV2ComputeTest):
_interface = 'json'
@classmethod
@@ -28,7 +28,7 @@
super(ServerAddressesTest, cls).setUpClass()
cls.client = cls.servers_client
- resp, cls.server = cls.create_server(wait_until='ACTIVE')
+ resp, cls.server = cls.create_test_server(wait_until='ACTIVE')
@attr(type=['negative', 'gate'])
def test_list_server_addresses_invalid_server_id(self):
diff --git a/tempest/api/compute/servers/test_server_metadata.py b/tempest/api/compute/servers/test_server_metadata.py
index 5ea3cbf..ee0f4a9 100644
--- a/tempest/api/compute/servers/test_server_metadata.py
+++ b/tempest/api/compute/servers/test_server_metadata.py
@@ -20,7 +20,7 @@
from tempest.test import attr
-class ServerMetadataTestJSON(base.BaseComputeTest):
+class ServerMetadataTestJSON(base.BaseV2ComputeTest):
_interface = 'json'
@classmethod
@@ -32,7 +32,7 @@
resp, tenants = cls.admin_client.list_tenants()
cls.tenant_id = [tnt['id'] for tnt in tenants if tnt['name'] ==
cls.client.tenant_name][0]
- resp, server = cls.create_server(meta={}, wait_until='ACTIVE')
+ resp, server = cls.create_test_server(meta={}, wait_until='ACTIVE')
cls.server_id = server['id']
@@ -76,19 +76,11 @@
key = "k" * sz
meta = {key: 'data1'}
self.assertRaises(exceptions.OverLimit,
- self.create_server,
+ self.create_test_server,
meta=meta)
# no teardown - all creates should fail
- @attr(type=['negative', 'gate'])
- def test_create_metadata_key_error(self):
- # Blank key should trigger an error.
- meta = {'': 'data1'}
- self.assertRaises(exceptions.BadRequest,
- self.create_server,
- meta=meta)
-
@attr(type='gate')
def test_update_server_metadata(self):
# The server's metadata values should be updated to the
@@ -147,62 +139,48 @@
self.assertEqual(expected, resp_metadata)
@attr(type=['negative', 'gate'])
- def test_get_nonexistant_server_metadata_item(self):
- # Negative test: GET on a non-existent server should not succeed
+ def test_server_metadata_negative(self):
+ # Blank key should trigger an error.
+ meta = {'': 'data1'}
+ self.assertRaises(exceptions.BadRequest,
+ self.create_test_server,
+ meta=meta)
+
+ # GET on a non-existent server should not succeed
self.assertRaises(exceptions.NotFound,
self.client.get_server_metadata_item, 999, 'test2')
- @attr(type=['negative', 'gate'])
- def test_list_nonexistant_server_metadata(self):
- # Negative test:List metadata on a non-existent server should
- # not succeed
+ # List metadata on a non-existent server should not succeed
self.assertRaises(exceptions.NotFound,
self.client.list_server_metadata, 999)
- @attr(type=['negative', 'gate'])
- def test_set_server_metadata_item_incorrect_uri_key(self):
# Raise BadRequest if key in uri does not match
# the key passed in body.
-
meta = {'testkey': 'testvalue'}
self.assertRaises(exceptions.BadRequest,
self.client.set_server_metadata_item,
self.server_id, 'key', meta)
- @attr(type=['negative', 'gate'])
- def test_set_nonexistant_server_metadata(self):
- # Negative test: Set metadata on a non-existent server should not
- # succeed
+ # Set metadata on a non-existent server should not succeed
meta = {'meta1': 'data1'}
self.assertRaises(exceptions.NotFound,
self.client.set_server_metadata, 999, meta)
- @attr(type=['negative', 'gate'])
- def test_update_nonexistant_server_metadata(self):
- # Negative test: An update should not happen for a non-existent image
+ # An update should not happen for a non-existent image
meta = {'key1': 'value1', 'key2': 'value2'}
self.assertRaises(exceptions.NotFound,
self.client.update_server_metadata, 999, meta)
- @attr(type=['negative', 'gate'])
- def test_update_metadata_key_error(self):
- # Blank key should trigger an error.
+ # Blank key should trigger an error
meta = {'': 'data1'}
self.assertRaises(exceptions.BadRequest,
self.client.update_server_metadata,
self.server_id, meta=meta)
- @attr(type=['negative', 'gate'])
- def test_delete_nonexistant_server_metadata_item(self):
- # Negative test: Should not be able to delete metadata item from a
- # non-existent server
-
- # Delete the metadata item
+ # Should not be able to delete metadata item from a non-existent server
self.assertRaises(exceptions.NotFound,
self.client.delete_server_metadata_item, 999, 'd')
- @attr(type=['negative', 'gate'])
- def test_set_server_metadata_too_long(self):
# Raise a 413 OverLimit exception while exceeding metadata items limit
# for tenant.
_, quota_set = self.quotas.get_quota_set(self.tenant_id)
@@ -214,21 +192,12 @@
self.client.set_server_metadata,
self.server_id, req_metadata)
- @attr(type=['negative', 'gate'])
- def test_update_server_metadata_too_long(self):
# Raise a 413 OverLimit exception while exceeding metadata items limit
- # for tenant.
- _, quota_set = self.quotas.get_quota_set(self.tenant_id)
- quota_metadata = quota_set['metadata_items']
- req_metadata = {}
- for num in range(1, quota_metadata + 2):
- req_metadata['key' + str(num)] = 'val' + str(num)
+ # for tenant (update).
self.assertRaises(exceptions.OverLimit,
self.client.update_server_metadata,
self.server_id, req_metadata)
- @attr(type=['negative', 'gate'])
- def test_update_all_metadata_field_error(self):
# Raise a bad request error for blank key.
# set_server_metadata will replace all metadata with new value
meta = {'': 'data1'}
@@ -236,6 +205,13 @@
self.client.set_server_metadata,
self.server_id, meta=meta)
+ # Raise a bad request error for a missing metadata field
+ # set_server_metadata will replace all metadata with new value
+ meta = {'meta1': 'data1'}
+ self.assertRaises(exceptions.BadRequest,
+ self.client.set_server_metadata,
+ self.server_id, meta=meta, no_metadata_field=True)
+
class ServerMetadataTestXML(ServerMetadataTestJSON):
_interface = 'xml'
diff --git a/tempest/api/compute/servers/test_server_password.py b/tempest/api/compute/servers/test_server_password.py
new file mode 100644
index 0000000..93c6e44
--- /dev/null
+++ b/tempest/api/compute/servers/test_server_password.py
@@ -0,0 +1,44 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 IBM Corporation
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+
+from tempest.api.compute import base
+from tempest.test import attr
+
+
+class ServerPasswordTestJSON(base.BaseV2ComputeTest):
+ _interface = 'json'
+
+ @classmethod
+ def setUpClass(cls):
+ super(ServerPasswordTestJSON, cls).setUpClass()
+ cls.client = cls.servers_client
+ resp, cls.server = cls.create_test_server(wait_until="ACTIVE")
+
+ @attr(type='gate')
+ def test_get_server_password(self):
+ resp, body = self.client.get_password(self.server['id'])
+ self.assertEqual(200, resp.status)
+
+ @attr(type='gate')
+ def test_delete_server_password(self):
+ resp, body = self.client.delete_password(self.server['id'])
+ self.assertEqual(204, resp.status)
+
+
+class ServerPasswordTestXML(ServerPasswordTestJSON):
+ _interface = 'xml'
diff --git a/tempest/api/compute/servers/test_server_personality.py b/tempest/api/compute/servers/test_server_personality.py
index 2019732..c6d2e44 100644
--- a/tempest/api/compute/servers/test_server_personality.py
+++ b/tempest/api/compute/servers/test_server_personality.py
@@ -22,7 +22,7 @@
from tempest.test import attr
-class ServerPersonalityTestJSON(base.BaseComputeTest):
+class ServerPersonalityTestJSON(base.BaseV2ComputeTest):
_interface = 'json'
@classmethod
@@ -43,7 +43,7 @@
path = 'etc/test' + str(i) + '.txt'
personality.append({'path': path,
'contents': base64.b64encode(file_contents)})
- self.assertRaises(exceptions.OverLimit, self.create_server,
+ self.assertRaises(exceptions.OverLimit, self.create_test_server,
personality=personality)
@attr(type='gate')
@@ -60,8 +60,7 @@
'path': path,
'contents': base64.b64encode(file_contents),
})
- resp, server = self.create_server(personality=person)
- self.addCleanup(self.client.delete_server, server['id'])
+ resp, server = self.create_test_server(personality=person)
self.assertEqual('202', resp['status'])
diff --git a/tempest/api/compute/servers/test_server_rescue.py b/tempest/api/compute/servers/test_server_rescue.py
index 82559d5..1008670 100644
--- a/tempest/api/compute/servers/test_server_rescue.py
+++ b/tempest/api/compute/servers/test_server_rescue.py
@@ -16,17 +16,14 @@
# under the License.
from tempest.api.compute import base
-from tempest.common.utils.data_utils import rand_name
-import tempest.config
+from tempest.common.utils import data_utils
from tempest import exceptions
from tempest.test import attr
-class ServerRescueTestJSON(base.BaseComputeTest):
+class ServerRescueTestJSON(base.BaseV2ComputeTest):
_interface = 'json'
- run_ssh = tempest.config.TempestConfig().compute.run_ssh
-
@classmethod
def setUpClass(cls):
super(ServerRescueTestJSON, cls).setUpClass()
@@ -38,8 +35,8 @@
cls.floating_ip = str(body['ip']).strip()
# Security group creation
- cls.sg_name = rand_name('sg')
- cls.sg_desc = rand_name('sg-desc')
+ cls.sg_name = data_utils.rand_name('sg')
+ cls.sg_desc = data_utils.rand_name('sg-desc')
resp, cls.sg = \
cls.security_groups_client.create_security_group(cls.sg_name,
cls.sg_desc)
@@ -62,12 +59,8 @@
cls.volume_to_detach['id'], 'available')
# Server for positive tests
- resp, server = cls.create_server(image_id=cls.image_ref,
- flavor=cls.flavor_ref,
- wait_until='BUILD')
- resp, resc_server = cls.create_server(image_id=cls.image_ref,
- flavor=cls.flavor_ref,
- wait_until='ACTIVE')
+ resp, server = cls.create_test_server(wait_until='BUILD')
+ resp, resc_server = cls.create_test_server(wait_until='ACTIVE')
cls.server_id = server['id']
cls.password = server['adminPass']
cls.servers_client.wait_for_server_status(cls.server_id, 'ACTIVE')
@@ -133,13 +126,13 @@
self.addCleanup(self._unpause, self.server_id)
self.assertEqual(202, resp.status)
self.servers_client.wait_for_server_status(self.server_id, 'PAUSED')
- self.assertRaises(exceptions.Duplicate,
+ self.assertRaises(exceptions.Conflict,
self.servers_client.rescue_server,
self.server_id)
@attr(type=['negative', 'gate'])
def test_rescued_vm_reboot(self):
- self.assertRaises(exceptions.Duplicate, self.servers_client.reboot,
+ self.assertRaises(exceptions.Conflict, self.servers_client.reboot,
self.rescue_id, 'HARD')
@attr(type=['negative', 'gate'])
@@ -151,7 +144,7 @@
@attr(type=['negative', 'gate'])
def test_rescued_vm_rebuild(self):
- self.assertRaises(exceptions.Duplicate,
+ self.assertRaises(exceptions.Conflict,
self.servers_client.rebuild,
self.rescue_id,
self.image_ref_alt)
@@ -164,7 +157,7 @@
self.addCleanup(self._unrescue, self.server_id)
# Attach the volume to the server
- self.assertRaises(exceptions.Duplicate,
+ self.assertRaises(exceptions.Conflict,
self.servers_client.attach_volume,
self.server_id,
self.volume_to_attach['id'],
@@ -188,7 +181,7 @@
self.addCleanup(self._unrescue, self.server_id)
# Detach the volume from the server expecting failure
- self.assertRaises(exceptions.Duplicate,
+ self.assertRaises(exceptions.Conflict,
self.servers_client.detach_volume,
self.server_id,
self.volume_to_detach['id'])
diff --git a/tempest/api/compute/servers/test_servers.py b/tempest/api/compute/servers/test_servers.py
index 5ce51c0..d72476d 100644
--- a/tempest/api/compute/servers/test_servers.py
+++ b/tempest/api/compute/servers/test_servers.py
@@ -16,11 +16,11 @@
# under the License.
from tempest.api.compute import base
-from tempest.common.utils.data_utils import rand_name
+from tempest.common.utils import data_utils
from tempest.test import attr
-class ServersTestJSON(base.BaseComputeTest):
+class ServersTestJSON(base.BaseV2ComputeTest):
_interface = 'json'
@classmethod
@@ -36,7 +36,7 @@
def test_create_server_with_admin_password(self):
# If an admin password is provided on server creation, the server's
# root password should be set to that password.
- resp, server = self.create_server(adminPass='testpassword')
+ resp, server = self.create_test_server(adminPass='testpassword')
# Verify the password is set correctly in the response
self.assertEqual('testpassword', server['adminPass'])
@@ -46,12 +46,12 @@
# Creating a server with a name that already exists is allowed
# TODO(sdague): clear out try, we do cleanup one layer up
- server_name = rand_name('server')
- resp, server = self.create_server(name=server_name,
- wait_until='ACTIVE')
+ server_name = data_utils.rand_name('server')
+ resp, server = self.create_test_server(name=server_name,
+ wait_until='ACTIVE')
id1 = server['id']
- resp, server = self.create_server(name=server_name,
- wait_until='ACTIVE')
+ resp, server = self.create_test_server(name=server_name,
+ wait_until='ACTIVE')
id2 = server['id']
self.assertNotEqual(id1, id2, "Did not create a new server")
resp, server = self.client.get_server(id1)
@@ -64,10 +64,10 @@
def test_create_specify_keypair(self):
# Specify a keypair while creating a server
- key_name = rand_name('key')
+ key_name = data_utils.rand_name('key')
resp, keypair = self.keypairs_client.create_keypair(key_name)
resp, body = self.keypairs_client.list_keypairs()
- resp, server = self.create_server(key_name=key_name)
+ resp, server = self.create_test_server(key_name=key_name)
self.assertEqual('202', resp['status'])
self.client.wait_for_server_status(server['id'], 'ACTIVE')
resp, server = self.client.get_server(server['id'])
@@ -76,7 +76,7 @@
@attr(type='gate')
def test_update_server_name(self):
# The server name should be changed to the the provided value
- resp, server = self.create_server(wait_until='ACTIVE')
+ resp, server = self.create_test_server(wait_until='ACTIVE')
# Update the server with a new name
resp, server = self.client.update_server(server['id'],
@@ -91,7 +91,7 @@
@attr(type='gate')
def test_update_access_server_address(self):
# The server's access addresses should reflect the provided values
- resp, server = self.create_server(wait_until='ACTIVE')
+ resp, server = self.create_test_server(wait_until='ACTIVE')
# Update the IPv4 and IPv6 access addresses
resp, body = self.client.update_server(server['id'],
@@ -108,17 +108,26 @@
@attr(type='gate')
def test_delete_server_while_in_building_state(self):
# Delete a server while it's VM state is Building
- resp, server = self.create_server(wait_until='BUILD')
+ resp, server = self.create_test_server(wait_until='BUILD')
resp, _ = self.client.delete_server(server['id'])
self.assertEqual('204', resp['status'])
@attr(type='gate')
def test_delete_active_server(self):
# Delete a server while it's VM state is Active
- resp, server = self.create_server(wait_until='ACTIVE')
+ resp, server = self.create_test_server(wait_until='ACTIVE')
resp, _ = self.client.delete_server(server['id'])
self.assertEqual('204', resp['status'])
+ @attr(type='gate')
+ def test_create_server_with_ipv6_addr_only(self):
+ # Create a server without an IPv4 address(only IPv6 address).
+ resp, server = self.create_test_server(accessIPv6='2001:2001::3')
+ self.assertEqual('202', resp['status'])
+ self.client.wait_for_server_status(server['id'], 'ACTIVE')
+ resp, server = self.client.get_server(server['id'])
+ self.assertEqual('2001:2001::3', server['accessIPv6'])
+
class ServersTestXML(ServersTestJSON):
_interface = 'xml'
diff --git a/tempest/api/compute/servers/test_servers_negative.py b/tempest/api/compute/servers/test_servers_negative.py
index 5d9a5ce..5ec0cbe 100644
--- a/tempest/api/compute/servers/test_servers_negative.py
+++ b/tempest/api/compute/servers/test_servers_negative.py
@@ -15,19 +15,27 @@
# License for the specific language governing permissions and limitations
# under the License.
+import base64
import sys
import uuid
from tempest.api.compute import base
from tempest import clients
-from tempest.common.utils.data_utils import rand_name
+from tempest.common.utils import data_utils
from tempest import exceptions
from tempest.test import attr
-class ServersNegativeTestJSON(base.BaseComputeTest):
+class ServersNegativeTestJSON(base.BaseV2ComputeTest):
_interface = 'json'
+ def setUp(self):
+ super(ServersNegativeTestJSON, self).setUp()
+ try:
+ self.client.wait_for_server_status(self.server_id, 'ACTIVE')
+ except Exception:
+ self.rebuild_server()
+
@classmethod
def setUpClass(cls):
super(ServersNegativeTestJSON, cls).setUpClass()
@@ -35,13 +43,15 @@
cls.img_client = cls.images_client
cls.alt_os = clients.AltManager()
cls.alt_client = cls.alt_os.servers_client
+ resp, server = cls.create_test_server(wait_until='ACTIVE')
+ cls.server_id = server['id']
@attr(type=['negative', 'gate'])
def test_server_name_blank(self):
# Create a server with name parameter empty
self.assertRaises(exceptions.BadRequest,
- self.create_server,
+ self.create_test_server,
name='')
@attr(type=['negative', 'gate'])
@@ -53,7 +63,7 @@
'contents': file_contents}]
self.assertRaises(exceptions.BadRequest,
- self.create_server,
+ self.create_test_server,
personality=person)
@attr(type=['negative', 'gate'])
@@ -61,7 +71,7 @@
# Create a server with an unknown image
self.assertRaises(exceptions.BadRequest,
- self.create_server,
+ self.create_test_server,
image_id=-1)
@attr(type=['negative', 'gate'])
@@ -69,7 +79,7 @@
# Create a server with an unknown flavor
self.assertRaises(exceptions.BadRequest,
- self.create_server,
+ self.create_test_server,
flavor=-1,)
@attr(type=['negative', 'gate'])
@@ -78,7 +88,7 @@
IPv4 = '1.1.1.1.1.1'
self.assertRaises(exceptions.BadRequest,
- self.create_server, accessIPv4=IPv4)
+ self.create_test_server, accessIPv4=IPv4)
@attr(type=['negative', 'gate'])
def test_invalid_ip_v6_address(self):
@@ -87,41 +97,75 @@
IPv6 = 'notvalid'
self.assertRaises(exceptions.BadRequest,
- self.create_server, accessIPv6=IPv6)
+ self.create_test_server, accessIPv6=IPv6)
@attr(type=['negative', 'gate'])
- def test_reboot_deleted_server(self):
- # Reboot a deleted server
- resp, server = self.create_server()
- self.server_id = server['id']
- self.client.delete_server(self.server_id)
- self.client.wait_for_server_termination(self.server_id)
+ def test_resize_nonexistent_server(self):
+ nonexistent_server = str(uuid.uuid4())
+ self.assertRaises(exceptions.NotFound,
+ self.client.resize,
+ nonexistent_server, self.flavor_ref)
+
+ @attr(type=['negative', 'gate'])
+ def test_resize_server_with_non_existent_flavor(self):
+ # Resize a server with non-existent flavor
+ nonexistent_flavor = str(uuid.uuid4())
+ self.assertRaises(exceptions.BadRequest, self.client.resize,
+ self.server_id, flavor_ref=nonexistent_flavor)
+
+ @attr(type=['negative', 'gate'])
+ def test_resize_server_with_null_flavor(self):
+ # Resize a server with null flavor
+ self.assertRaises(exceptions.BadRequest, self.client.resize,
+ self.server_id, flavor_ref="")
+
+ @attr(type=['negative', 'gate'])
+ def test_reboot_non_existent_server(self):
+ # Reboot a non existent server
+ nonexistent_server = str(uuid.uuid4())
self.assertRaises(exceptions.NotFound, self.client.reboot,
- self.server_id, 'SOFT')
+ nonexistent_server, 'SOFT')
@attr(type=['negative', 'gate'])
def test_pause_paused_server(self):
# Pause a paused server.
- resp, server = self.create_server(wait_until='ACTIVE')
- self.server_id = server['id']
self.client.pause_server(self.server_id)
+ self.addCleanup(self.client.unpause_server,
+ self.server_id)
self.client.wait_for_server_status(self.server_id, 'PAUSED')
- self.assertRaises(exceptions.Duplicate,
+ self.assertRaises(exceptions.Conflict,
self.client.pause_server,
self.server_id)
@attr(type=['negative', 'gate'])
- def test_rebuild_deleted_server(self):
- # Rebuild a deleted server
-
- resp, server = self.create_server()
- self.server_id = server['id']
- self.client.delete_server(self.server_id)
- self.client.wait_for_server_termination(self.server_id)
+ def test_rebuild_reboot_deleted_server(self):
+ # Rebuild and Reboot a deleted server
+ _, server = self.create_test_server()
+ self.client.delete_server(server['id'])
+ self.client.wait_for_server_termination(server['id'])
self.assertRaises(exceptions.NotFound,
self.client.rebuild,
- self.server_id, self.image_ref_alt)
+ server['id'], self.image_ref_alt)
+ self.assertRaises(exceptions.NotFound, self.client.reboot,
+ server['id'], 'SOFT')
+
+ @attr(type=['negative', 'gate'])
+ def test_rebuild_non_existent_server(self):
+ # Rebuild a non existent server
+ nonexistent_server = str(uuid.uuid4())
+ meta = {'rebuild': 'server'}
+ new_name = data_utils.rand_name('server')
+ file_contents = 'Test server rebuild.'
+ personality = [{'path': '/etc/rebuild.txt',
+ 'contents': base64.b64encode(file_contents)}]
+ self.assertRaises(exceptions.NotFound,
+ self.client.rebuild,
+ nonexistent_server,
+ self.image_ref_alt,
+ name=new_name, meta=meta,
+ personality=personality,
+ adminPass='rebuild')
@attr(type=['negative', 'gate'])
def test_create_numeric_server_name(self):
@@ -131,7 +175,7 @@
server_name = 12345
self.assertRaises(exceptions.BadRequest,
- self.create_server,
+ self.create_test_server,
name=server_name)
@attr(type=['negative', 'gate'])
@@ -140,7 +184,7 @@
server_name = 'a' * 256
self.assertRaises(exceptions.BadRequest,
- self.create_server,
+ self.create_test_server,
name=server_name)
@attr(type=['negative', 'gate'])
@@ -150,16 +194,16 @@
networks = [{'fixed_ip': '10.0.1.1', 'uuid': 'a-b-c-d-e-f-g-h-i-j'}]
self.assertRaises(exceptions.BadRequest,
- self.create_server,
+ self.create_test_server,
networks=networks)
@attr(type=['negative', 'gate'])
def test_create_with_non_existant_keypair(self):
# Pass a non-existent keypair while creating a server
- key_name = rand_name('key')
+ key_name = data_utils.rand_name('key')
self.assertRaises(exceptions.BadRequest,
- self.create_server,
+ self.create_test_server,
key_name=key_name)
@attr(type=['negative', 'gate'])
@@ -168,15 +212,15 @@
metadata = {'a': 'b' * 260}
self.assertRaises(exceptions.OverLimit,
- self.create_server,
+ self.create_test_server,
meta=metadata)
@attr(type=['negative', 'gate'])
def test_update_name_of_non_existent_server(self):
# Update name of a non-existent server
- server_name = rand_name('server')
- new_name = rand_name('server') + '_updated'
+ server_name = data_utils.rand_name('server')
+ new_name = data_utils.rand_name('server') + '_updated'
self.assertRaises(exceptions.NotFound, self.client.update_server,
server_name, name=new_name)
@@ -185,7 +229,7 @@
def test_update_server_set_empty_name(self):
# Update name of the server to an empty string
- server_name = rand_name('server')
+ server_name = data_utils.rand_name('server')
new_name = ''
self.assertRaises(exceptions.BadRequest, self.client.update_server,
@@ -195,21 +239,19 @@
def test_update_server_of_another_tenant(self):
# Update name of a server that belongs to another tenant
- resp, server = self.create_server(wait_until='ACTIVE')
- new_name = server['id'] + '_new'
+ new_name = self.server_id + '_new'
self.assertRaises(exceptions.NotFound,
- self.alt_client.update_server, server['id'],
+ self.alt_client.update_server, self.server_id,
name=new_name)
@attr(type=['negative', 'gate'])
def test_update_server_name_length_exceeds_256(self):
# Update name of server exceed the name length limit
- resp, server = self.create_server(wait_until='ACTIVE')
new_name = 'a' * 256
self.assertRaises(exceptions.BadRequest,
self.client.update_server,
- server['id'],
+ self.server_id,
name=new_name)
@attr(type=['negative', 'gate'])
@@ -222,10 +264,9 @@
@attr(type=['negative', 'gate'])
def test_delete_a_server_of_another_tenant(self):
# Delete a server that belongs to another tenant
- resp, server = self.create_server(wait_until='ACTIVE')
self.assertRaises(exceptions.NotFound,
self.alt_client.delete_server,
- server['id'])
+ self.server_id)
@attr(type=['negative', 'gate'])
def test_delete_server_pass_negative_id(self):
@@ -246,7 +287,7 @@
security_groups = [{'name': 'does_not_exist'}]
self.assertRaises(exceptions.BadRequest,
- self.create_server,
+ self.create_test_server,
security_groups=security_groups)
@attr(type=['negative', 'gate'])
@@ -259,66 +300,149 @@
@attr(type=['negative', 'gate'])
def test_stop_non_existent_server(self):
# Stop a non existent server
+ nonexistent_server = str(uuid.uuid4())
self.assertRaises(exceptions.NotFound, self.servers_client.stop,
- str(uuid.uuid4()))
+ nonexistent_server)
@attr(type=['negative', 'gate'])
def test_pause_non_existent_server(self):
# pause a non existent server
+ nonexistent_server = str(uuid.uuid4())
self.assertRaises(exceptions.NotFound, self.client.pause_server,
- str(uuid.uuid4()))
+ nonexistent_server)
@attr(type=['negative', 'gate'])
def test_unpause_non_existent_server(self):
# unpause a non existent server
+ nonexistent_server = str(uuid.uuid4())
self.assertRaises(exceptions.NotFound, self.client.unpause_server,
- str(uuid.uuid4()))
+ nonexistent_server)
@attr(type=['negative', 'gate'])
def test_unpause_server_invalid_state(self):
# unpause an active server.
- resp, server = self.create_server(wait_until='ACTIVE')
- server_id = server['id']
- self.assertRaises(exceptions.Duplicate,
+ self.assertRaises(exceptions.Conflict,
self.client.unpause_server,
- server_id)
+ self.server_id)
@attr(type=['negative', 'gate'])
def test_suspend_non_existent_server(self):
# suspend a non existent server
+ nonexistent_server = str(uuid.uuid4())
self.assertRaises(exceptions.NotFound, self.client.suspend_server,
- str(uuid.uuid4()))
+ nonexistent_server)
@attr(type=['negative', 'gate'])
def test_suspend_server_invalid_state(self):
- # create server.
- resp, server = self.create_server(wait_until='ACTIVE')
- server_id = server['id']
-
# suspend a suspended server.
- resp, _ = self.client.suspend_server(server_id)
+ resp, _ = self.client.suspend_server(self.server_id)
+ self.addCleanup(self.client.resume_server,
+ self.server_id)
self.assertEqual(202, resp.status)
- self.client.wait_for_server_status(server_id, 'SUSPENDED')
- self.assertRaises(exceptions.Duplicate,
+ self.client.wait_for_server_status(self.server_id, 'SUSPENDED')
+ self.assertRaises(exceptions.Conflict,
self.client.suspend_server,
- server_id)
+ self.server_id)
@attr(type=['negative', 'gate'])
def test_resume_non_existent_server(self):
# resume a non existent server
+ nonexistent_server = str(uuid.uuid4())
self.assertRaises(exceptions.NotFound, self.client.resume_server,
- str(uuid.uuid4()))
+ nonexistent_server)
@attr(type=['negative', 'gate'])
def test_resume_server_invalid_state(self):
- # create server.
- resp, server = self.create_server(wait_until='ACTIVE')
- server_id = server['id']
-
# resume an active server.
- self.assertRaises(exceptions.Duplicate,
+ self.assertRaises(exceptions.Conflict,
self.client.resume_server,
- server_id)
+ self.server_id)
+
+ @attr(type=['negative', 'gate'])
+ def test_get_console_output_of_non_existent_server(self):
+ # get the console output for a non existent server
+ nonexistent_server = str(uuid.uuid4())
+ self.assertRaises(exceptions.NotFound,
+ self.client.get_console_output,
+ nonexistent_server, 10)
+
+ @attr(type=['negative', 'gate'])
+ def test_force_delete_nonexistent_server_id(self):
+ non_existent_server_id = str(uuid.uuid4())
+
+ self.assertRaises(exceptions.NotFound,
+ self.client.force_delete_server,
+ non_existent_server_id)
+
+ @attr(type=['negative', 'gate'])
+ def test_force_delete_server_invalid_state(self):
+ # we can only force-delete a server in 'soft-delete' state
+ self.assertRaises(exceptions.Conflict,
+ self.client.force_delete_server,
+ self.server_id)
+
+ @attr(type=['negative', 'gate'])
+ def test_restore_nonexistent_server_id(self):
+ non_existent_server_id = str(uuid.uuid4())
+
+ self.assertRaises(exceptions.NotFound,
+ self.client.restore_soft_deleted_server,
+ non_existent_server_id)
+
+ @attr(type=['negative', 'gate'])
+ def test_restore_server_invalid_state(self):
+ # we can only restore-delete a server in 'soft-delete' state
+ self.assertRaises(exceptions.Conflict,
+ self.client.restore_soft_deleted_server,
+ self.server_id)
+
+ @attr(type=['negative', 'gate'])
+ def test_shelve_non_existent_server(self):
+ # shelve a non existent server
+ nonexistent_server = str(uuid.uuid4())
+ self.assertRaises(exceptions.NotFound, self.client.shelve_server,
+ nonexistent_server)
+
+ @attr(type=['negative', 'gate'])
+ def test_shelve_shelved_server(self):
+ # shelve a shelved server.
+ resp, server = self.client.shelve_server(self.server_id)
+ self.assertEqual(202, resp.status)
+ self.addCleanup(self.client.unshelve_server, self.server_id)
+
+ offload_time = self.config.compute.shelved_offload_time
+ if offload_time >= 0:
+ self.client.wait_for_server_status(self.server_id,
+ 'SHELVED_OFFLOADED',
+ extra_timeout=offload_time)
+ else:
+ self.client.wait_for_server_status(self.server_id,
+ 'SHELVED')
+
+ resp, server = self.client.get_server(self.server_id)
+ image_name = server['name'] + '-shelved'
+ params = {'name': image_name}
+ resp, images = self.images_client.list_images(params)
+ self.assertEqual(1, len(images))
+ self.assertEqual(image_name, images[0]['name'])
+
+ self.assertRaises(exceptions.Conflict,
+ self.client.shelve_server,
+ self.server_id)
+
+ @attr(type=['negative', 'gate'])
+ def test_unshelve_non_existent_server(self):
+ # unshelve a non existent server
+ nonexistent_server = str(uuid.uuid4())
+ self.assertRaises(exceptions.NotFound, self.client.unshelve_server,
+ nonexistent_server)
+
+ @attr(type=['negative', 'gate'])
+ def test_unshelve_server_invalid_state(self):
+ # unshelve an active server.
+ self.assertRaises(exceptions.Conflict,
+ self.client.unshelve_server,
+ self.server_id)
class ServersNegativeTestXML(ServersNegativeTestJSON):
diff --git a/tempest/api/compute/servers/test_virtual_interfaces.py b/tempest/api/compute/servers/test_virtual_interfaces.py
index 2c7ff32..77ada0b 100644
--- a/tempest/api/compute/servers/test_virtual_interfaces.py
+++ b/tempest/api/compute/servers/test_virtual_interfaces.py
@@ -18,14 +18,14 @@
import netaddr
from tempest.api.compute import base
-from tempest.common.utils.data_utils import rand_name
+from tempest.common.utils import data_utils
from tempest import config
from tempest import exceptions
from tempest.test import attr
from tempest.test import skip_because
-class VirtualInterfacesTestJSON(base.BaseComputeTest):
+class VirtualInterfacesTestJSON(base.BaseV2ComputeTest):
_interface = 'json'
CONF = config.TempestConfig()
@@ -34,7 +34,7 @@
def setUpClass(cls):
super(VirtualInterfacesTestJSON, cls).setUpClass()
cls.client = cls.servers_client
- resp, server = cls.create_server(wait_until='ACTIVE')
+ resp, server = cls.create_test_server(wait_until='ACTIVE')
cls.server_id = server['id']
@skip_because(bug="1183436",
@@ -58,7 +58,7 @@
def test_list_virtual_interfaces_invalid_server_id(self):
# Negative test: Should not be able to GET virtual interfaces
# for an invalid server_id
- invalid_server_id = rand_name('!@#$%^&*()')
+ invalid_server_id = data_utils.rand_name('!@#$%^&*()')
self.assertRaises(exceptions.NotFound,
self.client.list_virtual_interfaces,
invalid_server_id)
diff --git a/tempest/api/compute/test_auth_token.py b/tempest/api/compute/test_auth_token.py
index bbe92ef..ffeede8 100644
--- a/tempest/api/compute/test_auth_token.py
+++ b/tempest/api/compute/test_auth_token.py
@@ -19,7 +19,7 @@
import tempest.config as config
-class AuthTokenTestJSON(base.BaseComputeTest):
+class AuthTokenTestJSON(base.BaseV2ComputeTest):
_interface = 'json'
@classmethod
diff --git a/tempest/api/compute/test_authorization.py b/tempest/api/compute/test_authorization.py
index 0a8595f..49c4f32 100644
--- a/tempest/api/compute/test_authorization.py
+++ b/tempest/api/compute/test_authorization.py
@@ -18,8 +18,7 @@
from tempest.api import compute
from tempest.api.compute import base
from tempest import clients
-from tempest.common.utils.data_utils import parse_image_id
-from tempest.common.utils.data_utils import rand_name
+from tempest.common.utils import data_utils
from tempest import exceptions
from tempest.openstack.common import log as logging
from tempest.test import attr
@@ -27,7 +26,7 @@
LOG = logging.getLogger(__name__)
-class AuthorizationTestJSON(base.BaseComputeTest):
+class AuthorizationTestJSON(base.BaseV2ComputeTest):
_interface = 'json'
@classmethod
@@ -59,21 +58,21 @@
cls.alt_security_client = cls.alt_manager.security_groups_client
cls.alt_security_client._set_auth()
- resp, server = cls.create_server(wait_until='ACTIVE')
+ resp, server = cls.create_test_server(wait_until='ACTIVE')
resp, cls.server = cls.client.get_server(server['id'])
- name = rand_name('image')
+ name = data_utils.rand_name('image')
resp, body = cls.client.create_image(server['id'], name)
- image_id = parse_image_id(resp['location'])
+ image_id = data_utils.parse_image_id(resp['location'])
cls.images_client.wait_for_image_status(image_id, 'ACTIVE')
resp, cls.image = cls.images_client.get_image(image_id)
- cls.keypairname = rand_name('keypair')
+ cls.keypairname = data_utils.rand_name('keypair')
resp, keypair = \
cls.keypairs_client.create_keypair(cls.keypairname)
- name = rand_name('security')
- description = rand_name('description')
+ name = data_utils.rand_name('security')
+ description = data_utils.rand_name('description')
resp, cls.security_group = cls.security_client.create_security_group(
name, description)
@@ -191,7 +190,7 @@
# A create keypair request should fail if the tenant id does not match
# the current user
# POST keypair with other user tenant
- k_name = rand_name('keypair-')
+ k_name = data_utils.rand_name('keypair-')
self.alt_keypairs_client._set_auth()
self.saved_base_url = self.alt_keypairs_client.base_url
try:
@@ -241,8 +240,8 @@
# A create security group request should fail if the tenant id does not
# match the current user
# POST security group with other user tenant
- s_name = rand_name('security-')
- s_description = rand_name('security')
+ s_name = data_utils.rand_name('security-')
+ s_description = data_utils.rand_name('security')
self.saved_base_url = self.alt_security_client.base_url
try:
# Change the base URL to impersonate another user
diff --git a/tempest/api/compute/test_extensions.py b/tempest/api/compute/test_extensions.py
index c1b7aa5..8f1e446 100644
--- a/tempest/api/compute/test_extensions.py
+++ b/tempest/api/compute/test_extensions.py
@@ -20,7 +20,7 @@
from tempest.test import attr
-class ExtensionsTestJSON(base.BaseComputeTest):
+class ExtensionsTestJSON(base.BaseV2ComputeTest):
_interface = 'json'
@attr(type='gate')
diff --git a/tempest/api/compute/test_live_block_migration.py b/tempest/api/compute/test_live_block_migration.py
index 65daee0..a7b6cd2 100644
--- a/tempest/api/compute/test_live_block_migration.py
+++ b/tempest/api/compute/test_live_block_migration.py
@@ -26,7 +26,7 @@
from tempest.test import attr
-class LiveBlockMigrationTestJSON(base.BaseComputeAdminTest):
+class LiveBlockMigrationTestJSON(base.BaseV2ComputeAdminTest):
_host_key = 'OS-EXT-SRV-ATTR:host'
_interface = 'json'
@@ -59,7 +59,8 @@
def _migrate_server_to(self, server_id, dest_host):
_resp, body = self.admin_servers_client.live_migrate_server(
server_id, dest_host,
- self.config.compute.use_block_migration_for_live_migration)
+ self.config.compute_feature_enabled.
+ block_migration_for_live_migration)
return body
def _get_host_other_than(self, host):
@@ -83,7 +84,7 @@
if 'ACTIVE' == self._get_server_status(server_id):
return server_id
else:
- _, server = self.create_server(wait_until="ACTIVE")
+ _, server = self.create_test_server(wait_until="ACTIVE")
server_id = server['id']
self.password = server['adminPass']
self.password = 'password'
@@ -97,7 +98,7 @@
self.volumes_client.wait_for_volume_status(volume_id, 'available')
self.volumes_client.delete_volume(volume_id)
- @testtools.skipIf(not CONF.compute.live_migration_available,
+ @testtools.skipIf(not CONF.compute_feature_enabled.live_migration,
'Live migration not available')
@attr(type='gate')
def test_live_block_migration(self):
@@ -112,7 +113,7 @@
self.servers_client.wait_for_server_status(server_id, 'ACTIVE')
self.assertEqual(target_host, self._get_host_for_server(server_id))
- @testtools.skipIf(not CONF.compute.live_migration_available,
+ @testtools.skipIf(not CONF.compute_feature_enabled.live_migration,
'Live migration not available')
@attr(type='gate')
def test_invalid_host_for_migration(self):
@@ -124,10 +125,12 @@
server_id, target_host)
self.assertEqual('ACTIVE', self._get_server_status(server_id))
- @testtools.skipIf(not CONF.compute.live_migration_available or
- not CONF.compute.use_block_migration_for_live_migration,
+ @testtools.skipIf(not CONF.compute_feature_enabled.live_migration or not
+ CONF.compute_feature_enabled.
+ block_migration_for_live_migration,
'Block Live migration not available')
- @testtools.skipIf(not CONF.compute.block_migrate_supports_cinder_iscsi,
+ @testtools.skipIf(not CONF.compute_feature_enabled.
+ block_migrate_cinder_iscsi,
'Block Live migration not configured for iSCSI')
@attr(type='gate')
def test_iscsi_volume(self):
diff --git a/tempest/api/compute/test_quotas.py b/tempest/api/compute/test_quotas.py
index 6453cf4..475d055 100644
--- a/tempest/api/compute/test_quotas.py
+++ b/tempest/api/compute/test_quotas.py
@@ -19,7 +19,7 @@
from tempest.test import attr
-class QuotasTestJSON(base.BaseComputeTest):
+class QuotasTestJSON(base.BaseV2ComputeTest):
_interface = 'json'
@classmethod
@@ -58,6 +58,16 @@
sorted(quota_set.keys()))
self.assertEqual(quota_set['id'], self.tenant_id)
+ @attr(type='smoke')
+ def test_compare_tenant_quotas_with_default_quotas(self):
+ # Tenants are created with the default quota values
+ resp, defualt_quota_set = \
+ self.client.get_default_quota_set(self.tenant_id)
+ self.assertEqual(200, resp.status)
+ resp, tenant_quota_set = self.client.get_quota_set(self.tenant_id)
+ self.assertEqual(200, resp.status)
+ self.assertEqual(defualt_quota_set, tenant_quota_set)
+
class QuotasTestXML(QuotasTestJSON):
_interface = 'xml'
diff --git a/tempest/api/compute/v3/__init__.py b/tempest/api/compute/v3/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/api/compute/v3/__init__.py
diff --git a/tempest/api/compute/v3/admin/__init__.py b/tempest/api/compute/v3/admin/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/api/compute/v3/admin/__init__.py
diff --git a/tempest/api/compute/v3/admin/test_availability_zone.py b/tempest/api/compute/v3/admin/test_availability_zone.py
new file mode 100644
index 0000000..ff2765c
--- /dev/null
+++ b/tempest/api/compute/v3/admin/test_availability_zone.py
@@ -0,0 +1,70 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 NEC Corporation
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.api.compute import base
+from tempest import exceptions
+from tempest.test import attr
+
+
+class AvailabilityZoneAdminV3TestJSON(base.BaseV3ComputeAdminTest):
+
+ """
+ Tests Availability Zone API List that require admin privileges
+ """
+
+ _interface = 'json'
+
+ @classmethod
+ def setUpClass(cls):
+ super(AvailabilityZoneAdminV3TestJSON, cls).setUpClass()
+ cls.client = cls.availability_zone_admin_client
+ cls.non_adm_client = cls.availability_zone_client
+
+ @attr(type='gate')
+ def test_get_availability_zone_list(self):
+ # List of availability zone
+ resp, availability_zone = self.client.get_availability_zone_list()
+ self.assertEqual(200, resp.status)
+ self.assertTrue(len(availability_zone) > 0)
+
+ @attr(type='gate')
+ def test_get_availability_zone_list_detail(self):
+ # List of availability zones and available services
+ resp, availability_zone = \
+ self.client.get_availability_zone_list_detail()
+ self.assertEqual(200, resp.status)
+ self.assertTrue(len(availability_zone) > 0)
+
+ @attr(type='gate')
+ def test_get_availability_zone_list_with_non_admin_user(self):
+ # List of availability zone with non-administrator user
+ resp, availability_zone = \
+ self.non_adm_client.get_availability_zone_list()
+ self.assertEqual(200, resp.status)
+ self.assertTrue(len(availability_zone) > 0)
+
+ @attr(type=['negative', 'gate'])
+ def test_get_availability_zone_list_detail_with_non_admin_user(self):
+ # List of availability zones and available services with
+ # non-administrator user
+ self.assertRaises(
+ exceptions.Unauthorized,
+ self.non_adm_client.get_availability_zone_list_detail)
+
+
+class AvailabilityZoneAdminV3TestXML(AvailabilityZoneAdminV3TestJSON):
+ _interface = 'xml'
diff --git a/tempest/api/compute/v3/admin/test_services.py b/tempest/api/compute/v3/admin/test_services.py
new file mode 100644
index 0000000..67f9947
--- /dev/null
+++ b/tempest/api/compute/v3/admin/test_services.py
@@ -0,0 +1,135 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 NEC Corporation
+# Copyright 2013 IBM Corp.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.api.compute import base
+from tempest import exceptions
+from tempest.test import attr
+
+
+class ServicesAdminV3TestJSON(base.BaseV3ComputeAdminTest):
+
+ """
+ Tests Services API. List and Enable/Disable require admin privileges.
+ """
+
+ _interface = 'json'
+
+ @classmethod
+ def setUpClass(cls):
+ super(ServicesAdminV3TestJSON, cls).setUpClass()
+ cls.client = cls.services_admin_client
+ cls.non_admin_client = cls.services_client
+
+ @attr(type='gate')
+ def test_list_services(self):
+ resp, services = self.client.list_services()
+ self.assertEqual(200, resp.status)
+ self.assertNotEqual(0, len(services))
+
+ @attr(type=['negative', 'gate'])
+ def test_list_services_with_non_admin_user(self):
+ self.assertRaises(exceptions.Unauthorized,
+ self.non_admin_client.list_services)
+
+ @attr(type='gate')
+ def test_get_service_by_service_binary_name(self):
+ binary_name = 'nova-compute'
+ params = {'binary': binary_name}
+ resp, services = self.client.list_services(params)
+ self.assertEqual(200, resp.status)
+ self.assertNotEqual(0, len(services))
+ for service in services:
+ self.assertEqual(binary_name, service['binary'])
+
+ @attr(type='gate')
+ def test_get_service_by_host_name(self):
+ resp, services = self.client.list_services()
+ host_name = services[0]['host']
+ services_on_host = [service for service in services if
+ service['host'] == host_name]
+ params = {'host': host_name}
+ resp, services = self.client.list_services(params)
+
+ # we could have a periodic job checkin between the 2 service
+ # lookups, so only compare binary lists.
+ s1 = map(lambda x: x['binary'], services)
+ s2 = map(lambda x: x['binary'], services_on_host)
+
+ # sort the lists before comparing, to take out dependency
+ # on order.
+ self.assertEqual(sorted(s1), sorted(s2))
+
+ @attr(type=['negative', 'gate'])
+ def test_get_service_by_invalid_params(self):
+ # return all services if send the request with invalid parameter
+ resp, services = self.client.list_services()
+ params = {'xxx': 'nova-compute'}
+ resp, services_xxx = self.client.list_services(params)
+ self.assertEqual(200, resp.status)
+ self.assertEqual(len(services), len(services_xxx))
+
+ @attr(type='gate')
+ def test_get_service_by_service_and_host_name(self):
+ resp, services = self.client.list_services()
+ host_name = services[0]['host']
+ binary_name = services[0]['binary']
+ params = {'host': host_name, 'binary': binary_name}
+ resp, services = self.client.list_services(params)
+ self.assertEqual(200, resp.status)
+ self.assertEqual(1, len(services))
+ self.assertEqual(host_name, services[0]['host'])
+ self.assertEqual(binary_name, services[0]['binary'])
+
+ @attr(type=['negative', 'gate'])
+ def test_get_service_by_invalid_service_and_valid_host(self):
+ resp, services = self.client.list_services()
+ host_name = services[0]['host']
+ params = {'host': host_name, 'binary': 'xxx'}
+ resp, services = self.client.list_services(params)
+ self.assertEqual(200, resp.status)
+ self.assertEqual(0, len(services))
+
+ @attr(type=['negative', 'gate'])
+ def test_get_service_with_valid_service_and_invalid_host(self):
+ resp, services = self.client.list_services()
+ binary_name = services[0]['binary']
+ params = {'host': 'xxx', 'binary': binary_name}
+ resp, services = self.client.list_services(params)
+ self.assertEqual(200, resp.status)
+ self.assertEqual(0, len(services))
+
+ @attr(type='gate')
+ def test_service_enable_disable(self):
+ resp, services = self.client.list_services()
+ host_name = services[0]['host']
+ binary_name = services[0]['binary']
+
+ resp, service = self.client.disable_service(host_name, binary_name)
+ self.assertEqual(200, resp.status)
+ params = {'host': host_name, 'binary': binary_name}
+ resp, services = self.client.list_services(params)
+ self.assertEqual('disabled', services[0]['status'])
+
+ resp, service = self.client.enable_service(host_name, binary_name)
+ self.assertEqual(200, resp.status)
+ resp, services = self.client.list_services(params)
+ self.assertEqual('enabled', services[0]['status'])
+
+
+class ServicesAdminV3TestXML(ServicesAdminV3TestJSON):
+ _interface = 'xml'
diff --git a/tempest/api/compute/v3/admin/test_simple_tenant_usage.py b/tempest/api/compute/v3/admin/test_simple_tenant_usage.py
new file mode 100644
index 0000000..a599f06
--- /dev/null
+++ b/tempest/api/compute/v3/admin/test_simple_tenant_usage.py
@@ -0,0 +1,115 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 NEC Corporation
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import datetime
+
+from tempest.api.compute import base
+from tempest import exceptions
+from tempest.test import attr
+import time
+
+
+class TenantUsagesTestJSON(base.BaseV2ComputeAdminTest):
+
+ _interface = 'json'
+
+ @classmethod
+ def setUpClass(cls):
+ super(TenantUsagesTestJSON, cls).setUpClass()
+ cls.adm_client = cls.os_adm.tenant_usages_client
+ cls.client = cls.os.tenant_usages_client
+ cls.identity_client = cls._get_identity_admin_client()
+
+ resp, tenants = cls.identity_client.list_tenants()
+ cls.tenant_id = [tnt['id'] for tnt in tenants if tnt['name'] ==
+ cls.client.tenant_name][0]
+
+ # Create a server in the demo tenant
+ resp, server = cls.create_test_server(wait_until='ACTIVE')
+ time.sleep(2)
+
+ now = datetime.datetime.now()
+ cls.start = cls._parse_strtime(now - datetime.timedelta(days=1))
+ cls.end = cls._parse_strtime(now + datetime.timedelta(days=1))
+
+ @classmethod
+ def _parse_strtime(cls, at):
+ # Returns formatted datetime
+ return at.strftime('%Y-%m-%dT%H:%M:%S.%f')
+
+ @attr(type='gate')
+ def test_list_usage_all_tenants(self):
+ # Get usage for all tenants
+ params = {'start': self.start,
+ 'end': self.end,
+ 'detailed': int(bool(True))}
+ resp, tenant_usage = self.adm_client.list_tenant_usages(params)
+ self.assertEqual(200, resp.status)
+ self.assertEqual(len(tenant_usage), 8)
+
+ @attr(type='gate')
+ def test_get_usage_tenant(self):
+ # Get usage for a specific tenant
+ params = {'start': self.start,
+ 'end': self.end}
+ resp, tenant_usage = self.adm_client.get_tenant_usage(
+ self.tenant_id, params)
+
+ self.assertEqual(200, resp.status)
+ self.assertEqual(len(tenant_usage), 8)
+
+ @attr(type='gate')
+ def test_get_usage_tenant_with_non_admin_user(self):
+ # Get usage for a specific tenant with non admin user
+ params = {'start': self.start,
+ 'end': self.end}
+ resp, tenant_usage = self.client.get_tenant_usage(
+ self.tenant_id, params)
+
+ self.assertEqual(200, resp.status)
+ self.assertEqual(len(tenant_usage), 8)
+
+ @attr(type=['negative', 'gate'])
+ def test_get_usage_tenant_with_empty_tenant_id(self):
+ # Get usage for a specific tenant empty
+ params = {'start': self.start,
+ 'end': self.end}
+ self.assertRaises(exceptions.NotFound,
+ self.adm_client.get_tenant_usage,
+ '', params)
+
+ @attr(type=['negative', 'gate'])
+ def test_get_usage_tenant_with_invalid_date(self):
+ # Get usage for tenant with invalid date
+ params = {'start': self.end,
+ 'end': self.start}
+ self.assertRaises(exceptions.BadRequest,
+ self.adm_client.get_tenant_usage,
+ self.tenant_id, params)
+
+ @attr(type=['negative', 'gate'])
+ def test_list_usage_all_tenants_with_non_admin_user(self):
+ # Get usage for all tenants with non admin user
+ params = {'start': self.start,
+ 'end': self.end,
+ 'detailed': int(bool(True))}
+ self.assertRaises(exceptions.Unauthorized,
+ self.client.list_tenant_usages, params)
+
+
+class TenantUsagesTestXML(TenantUsagesTestJSON):
+ _interface = 'xml'
diff --git a/tempest/api/compute/v3/images/__init__.py b/tempest/api/compute/v3/images/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/api/compute/v3/images/__init__.py
diff --git a/tempest/api/compute/v3/images/test_images.py b/tempest/api/compute/v3/images/test_images.py
new file mode 100644
index 0000000..3aacafb
--- /dev/null
+++ b/tempest/api/compute/v3/images/test_images.py
@@ -0,0 +1,127 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 OpenStack Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.api import compute
+from tempest.api.compute import base
+from tempest import clients
+from tempest.common.utils import data_utils
+from tempest import exceptions
+from tempest.test import attr
+
+
+class ImagesV3TestJSON(base.BaseV3ComputeTest):
+ _interface = 'json'
+
+ @classmethod
+ def setUpClass(cls):
+ super(ImagesV3TestJSON, cls).setUpClass()
+ if not cls.config.service_available.glance:
+ skip_msg = ("%s skipped as glance is not available" % cls.__name__)
+ raise cls.skipException(skip_msg)
+ cls.client = cls.images_client
+ cls.servers_client = cls.servers_client
+
+ if compute.MULTI_USER:
+ if cls.config.compute.allow_tenant_isolation:
+ creds = cls.isolated_creds.get_alt_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
+
+ def __create_image__(self, server_id, name, meta=None):
+ resp, body = self.servers_client.create_image(server_id, name, meta)
+ image_id = data_utils.parse_image_id(resp['location'])
+ self.addCleanup(self.client.delete_image, image_id)
+ self.client.wait_for_image_status(image_id, 'ACTIVE')
+ return resp, body
+
+ @attr(type=['negative', 'gate'])
+ def test_create_image_from_deleted_server(self):
+ # An image should not be created if the server instance is removed
+ resp, server = self.create_test_server(wait_until='ACTIVE')
+
+ # Delete server before trying to create server
+ self.servers_client.delete_server(server['id'])
+ self.servers_client.wait_for_server_termination(server['id'])
+ # Create a new image after server is deleted
+ name = data_utils.rand_name('image')
+ meta = {'image_type': 'test'}
+ self.assertRaises(exceptions.NotFound,
+ self.__create_image__,
+ server['id'], name, meta)
+
+ @attr(type=['negative', 'gate'])
+ def test_create_image_from_invalid_server(self):
+ # An image should not be created with invalid server id
+ # Create a new image with invalid server id
+ name = data_utils.rand_name('image')
+ meta = {'image_type': 'test'}
+ resp = {}
+ resp['status'] = None
+ self.assertRaises(exceptions.NotFound, self.__create_image__,
+ '!@#$%^&*()', name, meta)
+
+ @attr(type=['negative', 'gate'])
+ def test_create_image_from_stopped_server(self):
+ resp, server = self.create_test_server(wait_until='ACTIVE')
+ self.servers_client.stop(server['id'])
+ self.servers_client.wait_for_server_status(server['id'],
+ 'SHUTOFF')
+ self.addCleanup(self.servers_client.delete_server, server['id'])
+ snapshot_name = data_utils.rand_name('test-snap-')
+ resp, image = self.create_image_from_server(server['id'],
+ name=snapshot_name,
+ wait_until='active')
+ self.addCleanup(self.client.delete_image, image['id'])
+ self.assertEqual(snapshot_name, image['name'])
+
+ @attr(type='gate')
+ def test_delete_queued_image(self):
+ snapshot_name = data_utils.rand_name('test-snap-')
+ resp, server = self.create_test_server(wait_until='ACTIVE')
+ self.addCleanup(self.servers_client.delete_server, server['id'])
+ resp, image = self.create_image_from_server(server['id'],
+ name=snapshot_name,
+ wait_until='queued')
+ resp, body = self.client.delete_image(image['id'])
+ self.assertEqual('200', resp['status'])
+
+ @attr(type=['negative', 'gate'])
+ def test_create_image_specify_uuid_35_characters_or_less(self):
+ # Return an error if Image ID passed is 35 characters or less
+ snapshot_name = data_utils.rand_name('test-snap-')
+ test_uuid = ('a' * 35)
+ self.assertRaises(exceptions.NotFound,
+ self.servers_client.create_image,
+ test_uuid, snapshot_name)
+
+ @attr(type=['negative', 'gate'])
+ def test_create_image_specify_uuid_37_characters_or_more(self):
+ # Return an error if Image ID passed is 37 characters or more
+ snapshot_name = data_utils.rand_name('test-snap-')
+ test_uuid = ('a' * 37)
+ self.assertRaises(exceptions.NotFound,
+ self.servers_client.create_image,
+ test_uuid, snapshot_name)
+
+
+class ImagesV3TestXML(ImagesV3TestJSON):
+ _interface = 'xml'
diff --git a/tempest/api/compute/v3/servers/__init__.py b/tempest/api/compute/v3/servers/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/api/compute/v3/servers/__init__.py
diff --git a/tempest/api/compute/v3/servers/test_attach_interfaces.py b/tempest/api/compute/v3/servers/test_attach_interfaces.py
new file mode 100644
index 0000000..f208a4b
--- /dev/null
+++ b/tempest/api/compute/v3/servers/test_attach_interfaces.py
@@ -0,0 +1,118 @@
+# Copyright 2013 IBM Corp.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.api.compute import base
+from tempest.test import attr
+
+import time
+
+
+class AttachInterfacesV3TestJSON(base.BaseV3ComputeTest):
+ _interface = 'json'
+
+ @classmethod
+ def setUpClass(cls):
+ if not cls.config.service_available.neutron:
+ raise cls.skipException("Neutron is required")
+ super(AttachInterfacesV3TestJSON, cls).setUpClass()
+ cls.client = cls.interfaces_client
+
+ def _check_interface(self, iface, port_id=None, network_id=None,
+ fixed_ip=None):
+ self.assertIn('port_state', iface)
+ if port_id:
+ self.assertEqual(iface['port_id'], port_id)
+ if network_id:
+ self.assertEqual(iface['net_id'], network_id)
+ if fixed_ip:
+ self.assertEqual(iface['fixed_ips'][0]['ip_address'], fixed_ip)
+
+ def _create_server_get_interfaces(self):
+ resp, server = self.create_test_server(wait_until='ACTIVE')
+ resp, ifs = self.client.list_interfaces(server['id'])
+ resp, body = self.client.wait_for_interface_status(
+ server['id'], ifs[0]['port_id'], 'ACTIVE')
+ ifs[0]['port_state'] = body['port_state']
+ return server, ifs
+
+ def _test_create_interface(self, server):
+ resp, iface = self.client.create_interface(server['id'])
+ resp, iface = self.client.wait_for_interface_status(
+ server['id'], iface['port_id'], 'ACTIVE')
+ self._check_interface(iface)
+ return iface
+
+ def _test_create_interface_by_network_id(self, server, ifs):
+ network_id = ifs[0]['net_id']
+ resp, iface = self.client.create_interface(server['id'],
+ network_id=network_id)
+ resp, iface = self.client.wait_for_interface_status(
+ server['id'], iface['port_id'], 'ACTIVE')
+ self._check_interface(iface, network_id=network_id)
+ return iface
+
+ def _test_show_interface(self, server, ifs):
+ iface = ifs[0]
+ resp, _iface = self.client.show_interface(server['id'],
+ iface['port_id'])
+ self.assertEqual(iface, _iface)
+
+ def _test_delete_interface(self, server, ifs):
+ # NOTE(danms): delete not the first or last, but one in the middle
+ iface = ifs[1]
+ self.client.delete_interface(server['id'], iface['port_id'])
+ for i in range(0, 5):
+ _r, _ifs = self.client.list_interfaces(server['id'])
+ if len(ifs) != len(_ifs):
+ break
+ time.sleep(1)
+
+ self.assertEqual(len(_ifs), len(ifs) - 1)
+ for _iface in _ifs:
+ self.assertNotEqual(iface['port_id'], _iface['port_id'])
+ return _ifs
+
+ def _compare_iface_list(self, list1, list2):
+ # NOTE(danms): port_state will likely have changed, so just
+ # confirm the port_ids are the same at least
+ list1 = [x['port_id'] for x in list1]
+ list2 = [x['port_id'] for x in list2]
+
+ self.assertEqual(sorted(list1), sorted(list2))
+
+ @attr(type='gate')
+ def test_create_list_show_delete_interfaces(self):
+ server, ifs = self._create_server_get_interfaces()
+ interface_count = len(ifs)
+ self.assertTrue(interface_count > 0)
+ self._check_interface(ifs[0])
+
+ iface = self._test_create_interface(server)
+ ifs.append(iface)
+
+ iface = self._test_create_interface_by_network_id(server, ifs)
+ ifs.append(iface)
+
+ resp, _ifs = self.client.list_interfaces(server['id'])
+ self._compare_iface_list(ifs, _ifs)
+
+ self._test_show_interface(server, ifs)
+
+ _ifs = self._test_delete_interface(server, ifs)
+ self.assertEqual(len(ifs) - 1, len(_ifs))
+
+
+class AttachInterfacesV3TestXML(AttachInterfacesV3TestJSON):
+ _interface = 'xml'
diff --git a/tempest/api/compute/v3/servers/test_instance_actions.py b/tempest/api/compute/v3/servers/test_instance_actions.py
new file mode 100644
index 0000000..ea92c9f
--- /dev/null
+++ b/tempest/api/compute/v3/servers/test_instance_actions.py
@@ -0,0 +1,69 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 NEC Corporation
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.api.compute import base
+from tempest import exceptions
+from tempest.test import attr
+
+
+class InstanceActionsV3TestJSON(base.BaseV3ComputeTest):
+ _interface = 'json'
+
+ @classmethod
+ def setUpClass(cls):
+ super(InstanceActionsV3TestJSON, cls).setUpClass()
+ cls.client = cls.servers_client
+ resp, server = cls.create_test_server(wait_until='ACTIVE')
+ cls.request_id = resp['x-compute-request-id']
+ cls.server_id = server['id']
+
+ @attr(type='gate')
+ def test_list_instance_actions(self):
+ # List actions of the provided server
+ resp, body = self.client.reboot(self.server_id, 'HARD')
+ self.client.wait_for_server_status(self.server_id, 'ACTIVE')
+
+ resp, body = self.client.list_instance_actions(self.server_id)
+ self.assertEqual(200, resp.status)
+ self.assertTrue(len(body) == 2, str(body))
+ self.assertTrue(any([i for i in body if i['action'] == 'create']))
+ self.assertTrue(any([i for i in body if i['action'] == 'reboot']))
+
+ @attr(type='gate')
+ def test_get_instance_action(self):
+ # Get the action details of the provided server
+ resp, body = self.client.get_instance_action(self.server_id,
+ self.request_id)
+ self.assertEqual(200, resp.status)
+ self.assertEqual(self.server_id, body['instance_uuid'])
+ self.assertEqual('create', body['action'])
+
+ @attr(type=['negative', 'gate'])
+ def test_list_instance_actions_invalid_server(self):
+ # List actions of the invalid server id
+ self.assertRaises(exceptions.NotFound,
+ self.client.list_instance_actions, 'server-999')
+
+ @attr(type=['negative', 'gate'])
+ def test_get_instance_action_invalid_request(self):
+ # Get the action details of the provided server with invalid request
+ self.assertRaises(exceptions.NotFound, self.client.get_instance_action,
+ self.server_id, '999')
+
+
+class InstanceActionsV3TestXML(InstanceActionsV3TestJSON):
+ _interface = 'xml'
diff --git a/tempest/api/compute/v3/servers/test_list_server_filters.py b/tempest/api/compute/v3/servers/test_list_server_filters.py
new file mode 100644
index 0000000..d333a1d
--- /dev/null
+++ b/tempest/api/compute/v3/servers/test_list_server_filters.py
@@ -0,0 +1,239 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 OpenStack Foundation
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.api.compute import base
+from tempest.api import utils
+from tempest.common.utils import data_utils
+from tempest import config
+from tempest import exceptions
+from tempest.test import attr
+from tempest.test import skip_because
+
+
+class ListServerFiltersV3TestJSON(base.BaseV3ComputeTest):
+ _interface = 'json'
+
+ @classmethod
+ def setUpClass(cls):
+ super(ListServerFiltersV3TestJSON, cls).setUpClass()
+ cls.client = cls.servers_client
+
+ # Check to see if the alternate image ref actually exists...
+ images_client = cls.images_client
+ resp, images = images_client.image_list()
+
+ if cls.image_ref != cls.image_ref_alt and \
+ any([image for image in images
+ if image['id'] == cls.image_ref_alt]):
+ cls.multiple_images = True
+ else:
+ cls.image_ref_alt = cls.image_ref
+
+ # Do some sanity checks here. If one of the images does
+ # not exist, fail early since the tests won't work...
+ try:
+ cls.images_client.get_image_meta(cls.image_ref)
+ except exceptions.NotFound:
+ raise RuntimeError("Image %s (image_ref) was not found!" %
+ cls.image_ref)
+
+ try:
+ cls.images_client.get_image_meta(cls.image_ref_alt)
+ except exceptions.NotFound:
+ raise RuntimeError("Image %s (image_ref_alt) was not found!" %
+ cls.image_ref_alt)
+
+ cls.s1_name = data_utils.rand_name(cls.__name__ + '-instance')
+ resp, cls.s1 = cls.create_test_server(name=cls.s1_name,
+ wait_until='ACTIVE')
+
+ cls.s2_name = data_utils.rand_name(cls.__name__ + '-instance')
+ resp, cls.s2 = cls.create_test_server(name=cls.s2_name,
+ image_id=cls.image_ref_alt,
+ wait_until='ACTIVE')
+
+ cls.s3_name = data_utils.rand_name(cls.__name__ + '-instance')
+ resp, cls.s3 = cls.create_test_server(name=cls.s3_name,
+ flavor=cls.flavor_ref_alt,
+ wait_until='ACTIVE')
+
+ cls.fixed_network_name = cls.config.compute.fixed_network_name
+
+ @utils.skip_unless_attr('multiple_images', 'Only one image found')
+ @attr(type='gate')
+ def test_list_servers_filter_by_image(self):
+ # Filter the list of servers by image
+ params = {'image': self.image_ref}
+ resp, body = self.client.list_servers(params)
+ servers = body['servers']
+
+ self.assertIn(self.s1['id'], map(lambda x: x['id'], servers))
+ self.assertNotIn(self.s2['id'], map(lambda x: x['id'], servers))
+ self.assertIn(self.s3['id'], map(lambda x: x['id'], servers))
+
+ @attr(type='gate')
+ def test_list_servers_filter_by_flavor(self):
+ # Filter the list of servers by flavor
+ params = {'flavor': self.flavor_ref_alt}
+ resp, body = self.client.list_servers(params)
+ servers = body['servers']
+
+ self.assertNotIn(self.s1['id'], map(lambda x: x['id'], servers))
+ self.assertNotIn(self.s2['id'], map(lambda x: x['id'], servers))
+ self.assertIn(self.s3['id'], map(lambda x: x['id'], servers))
+
+ @attr(type='gate')
+ def test_list_servers_filter_by_server_name(self):
+ # Filter the list of servers by server name
+ params = {'name': self.s1_name}
+ resp, body = self.client.list_servers(params)
+ servers = body['servers']
+
+ self.assertIn(self.s1_name, map(lambda x: x['name'], servers))
+ self.assertNotIn(self.s2_name, map(lambda x: x['name'], servers))
+ self.assertNotIn(self.s3_name, map(lambda x: x['name'], servers))
+
+ @attr(type='gate')
+ def test_list_servers_filter_by_server_status(self):
+ # Filter the list of servers by server status
+ params = {'status': 'active'}
+ resp, body = self.client.list_servers(params)
+ servers = body['servers']
+
+ self.assertIn(self.s1['id'], map(lambda x: x['id'], servers))
+ self.assertIn(self.s2['id'], map(lambda x: x['id'], servers))
+ self.assertIn(self.s3['id'], map(lambda x: x['id'], servers))
+
+ @attr(type='gate')
+ def test_list_servers_filter_by_limit(self):
+ # Verify only the expected number of servers are returned
+ params = {'limit': 1}
+ resp, servers = self.client.list_servers(params)
+ # when _interface='xml', one element for servers_links in servers
+ self.assertEqual(1, len([x for x in servers['servers'] if 'id' in x]))
+
+ @utils.skip_unless_attr('multiple_images', 'Only one image found')
+ @attr(type='gate')
+ def test_list_servers_detailed_filter_by_image(self):
+ # Filter the detailed list of servers by image
+ params = {'image': self.image_ref}
+ resp, body = self.client.list_servers_with_detail(params)
+ servers = body['servers']
+
+ self.assertIn(self.s1['id'], map(lambda x: x['id'], servers))
+ self.assertNotIn(self.s2['id'], map(lambda x: x['id'], servers))
+ self.assertIn(self.s3['id'], map(lambda x: x['id'], servers))
+
+ @attr(type='gate')
+ def test_list_servers_detailed_filter_by_flavor(self):
+ # Filter the detailed list of servers by flavor
+ params = {'flavor': self.flavor_ref_alt}
+ resp, body = self.client.list_servers_with_detail(params)
+ servers = body['servers']
+
+ self.assertNotIn(self.s1['id'], map(lambda x: x['id'], servers))
+ self.assertNotIn(self.s2['id'], map(lambda x: x['id'], servers))
+ self.assertIn(self.s3['id'], map(lambda x: x['id'], servers))
+
+ @attr(type='gate')
+ def test_list_servers_detailed_filter_by_server_name(self):
+ # Filter the detailed list of servers by server name
+ params = {'name': self.s1_name}
+ resp, body = self.client.list_servers_with_detail(params)
+ servers = body['servers']
+
+ self.assertIn(self.s1_name, map(lambda x: x['name'], servers))
+ self.assertNotIn(self.s2_name, map(lambda x: x['name'], servers))
+ self.assertNotIn(self.s3_name, map(lambda x: x['name'], servers))
+
+ @attr(type='gate')
+ def test_list_servers_detailed_filter_by_server_status(self):
+ # Filter the detailed list of servers by server status
+ params = {'status': 'active'}
+ resp, body = self.client.list_servers_with_detail(params)
+ expected_servers = (self.s1['id'], self.s2['id'], self.s3['id'])
+ servers = [x for x in body['servers'] if x['id'] in expected_servers]
+
+ self.assertIn(self.s1['id'], map(lambda x: x['id'], servers))
+ self.assertIn(self.s2['id'], map(lambda x: x['id'], servers))
+ self.assertIn(self.s3['id'], map(lambda x: x['id'], servers))
+ self.assertEqual(['ACTIVE'] * 3, [x['status'] for x in servers])
+
+ @attr(type='gate')
+ def test_list_servers_filtered_by_name_wildcard(self):
+ # List all servers that contains '-instance' in name
+ params = {'name': '-instance'}
+ resp, body = self.client.list_servers(params)
+ servers = body['servers']
+
+ self.assertIn(self.s1_name, map(lambda x: x['name'], servers))
+ self.assertIn(self.s2_name, map(lambda x: x['name'], servers))
+ self.assertIn(self.s3_name, map(lambda x: x['name'], servers))
+
+ # Let's take random part of name and try to search it
+ part_name = self.s1_name[6:-1]
+
+ params = {'name': part_name}
+ resp, body = self.client.list_servers(params)
+ servers = body['servers']
+
+ self.assertIn(self.s1_name, map(lambda x: x['name'], servers))
+ self.assertNotIn(self.s2_name, map(lambda x: x['name'], servers))
+ self.assertNotIn(self.s3_name, map(lambda x: x['name'], servers))
+
+ @skip_because(bug="1170718")
+ @attr(type='gate')
+ def test_list_servers_filtered_by_ip(self):
+ # Filter servers by ip
+ # Here should be listed 1 server
+ resp, self.s1 = self.client.get_server(self.s1['id'])
+ ip = self.s1['addresses'][self.fixed_network_name][0]['addr']
+ params = {'ip': ip}
+ resp, body = self.client.list_servers(params)
+ servers = body['servers']
+
+ self.assertIn(self.s1_name, map(lambda x: x['name'], servers))
+ self.assertNotIn(self.s2_name, map(lambda x: x['name'], servers))
+ self.assertNotIn(self.s3_name, map(lambda x: x['name'], servers))
+
+ @skip_because(bug="1182883",
+ condition=config.TempestConfig().service_available.neutron)
+ @attr(type='gate')
+ def test_list_servers_filtered_by_ip_regex(self):
+ # Filter servers by regex ip
+ # List all servers filtered by part of ip address.
+ # Here should be listed all servers
+ resp, self.s1 = self.client.get_server(self.s1['id'])
+ ip = self.s1['addresses'][self.fixed_network_name][0]['addr'][0:-3]
+ params = {'ip': ip}
+ resp, body = self.client.list_servers(params)
+ servers = body['servers']
+
+ self.assertIn(self.s1_name, map(lambda x: x['name'], servers))
+ self.assertIn(self.s2_name, map(lambda x: x['name'], servers))
+ self.assertIn(self.s3_name, map(lambda x: x['name'], servers))
+
+ @attr(type='gate')
+ def test_list_servers_detailed_limit_results(self):
+ # Verify only the expected number of detailed results are returned
+ params = {'limit': 1}
+ resp, servers = self.client.list_servers_with_detail(params)
+ self.assertEqual(1, len(servers['servers']))
+
+
+class ListServerFiltersV3TestXML(ListServerFiltersV3TestJSON):
+ _interface = 'xml'
diff --git a/tempest/api/compute/v3/servers/test_list_servers_negative.py b/tempest/api/compute/v3/servers/test_list_servers_negative.py
new file mode 100644
index 0000000..6225345
--- /dev/null
+++ b/tempest/api/compute/v3/servers/test_list_servers_negative.py
@@ -0,0 +1,222 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 OpenStack Foundation
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import datetime
+
+from tempest.api import compute
+from tempest.api.compute import base
+from tempest import clients
+from tempest import exceptions
+from tempest.test import attr
+
+
+class ListServersNegativeV3TestJSON(base.BaseV3ComputeTest):
+ _interface = 'json'
+
+ @classmethod
+ def _ensure_no_servers(cls, servers, username, tenant_name):
+ """
+ If there are servers and there is tenant isolation then a
+ skipException is raised to skip the test since it requires no servers
+ to already exist for the given user/tenant.
+ If there are servers and there is not tenant isolation then the test
+ blocks while the servers are being deleted.
+ """
+ if len(servers):
+ if not cls.config.compute.allow_tenant_isolation:
+ for srv in servers:
+ cls.client.wait_for_server_termination(srv['id'],
+ ignore_error=True)
+ else:
+ msg = ("User/tenant %(u)s/%(t)s already have "
+ "existing server instances. Skipping test." %
+ {'u': username, 't': tenant_name})
+ raise cls.skipException(msg)
+
+ @classmethod
+ def setUpClass(cls):
+ super(ListServersNegativeV3TestJSON, cls).setUpClass()
+ cls.client = cls.servers_client
+ cls.servers = []
+
+ if compute.MULTI_USER:
+ if cls.config.compute.allow_tenant_isolation:
+ creds = cls.isolated_creds.get_alt_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.servers_client
+
+ # Under circumstances when there is not a tenant/user
+ # created for the test case, the test case checks
+ # to see if there are existing servers for the
+ # either the normal user/tenant or the alt user/tenant
+ # and if so, the whole test is skipped. We do this
+ # because we assume a baseline of no servers at the
+ # start of the test instead of destroying any existing
+ # servers.
+ resp, body = cls.client.list_servers()
+ cls._ensure_no_servers(body['servers'],
+ cls.os.username,
+ cls.os.tenant_name)
+
+ resp, body = cls.alt_client.list_servers()
+ cls._ensure_no_servers(body['servers'],
+ cls.alt_manager.username,
+ cls.alt_manager.tenant_name)
+
+ # The following servers are created for use
+ # by the test methods in this class. These
+ # servers are cleaned up automatically in the
+ # tearDownClass method of the super-class.
+ cls.existing_fixtures = []
+ cls.deleted_fixtures = []
+ cls.start_time = datetime.datetime.utcnow()
+ for x in xrange(2):
+ resp, srv = cls.create_test_server()
+ cls.existing_fixtures.append(srv)
+
+ resp, srv = cls.create_test_server()
+ cls.client.delete_server(srv['id'])
+ # We ignore errors on termination because the server may
+ # be put into ERROR status on a quick spawn, then delete,
+ # as the compute node expects the instance local status
+ # to be spawning, not deleted. See LP Bug#1061167
+ cls.client.wait_for_server_termination(srv['id'],
+ ignore_error=True)
+ cls.deleted_fixtures.append(srv)
+
+ @attr(type=['negative', 'gate'])
+ def test_list_servers_with_a_deleted_server(self):
+ # Verify deleted servers do not show by default in list servers
+ # List servers and verify server not returned
+ resp, body = self.client.list_servers()
+ servers = body['servers']
+ deleted_ids = [s['id'] for s in self.deleted_fixtures]
+ actual = [srv for srv in servers
+ if srv['id'] in deleted_ids]
+ self.assertEqual('200', resp['status'])
+ self.assertEqual([], actual)
+
+ @attr(type=['negative', 'gate'])
+ def test_list_servers_by_non_existing_image(self):
+ # Listing servers for a non existing image returns empty list
+ non_existing_image = '1234abcd-zzz0-aaa9-ppp3-0987654abcde'
+ resp, body = self.client.list_servers(dict(image=non_existing_image))
+ servers = body['servers']
+ self.assertEqual('200', resp['status'])
+ self.assertEqual([], servers)
+
+ @attr(type=['negative', 'gate'])
+ def test_list_servers_by_non_existing_flavor(self):
+ # Listing servers by non existing flavor returns empty list
+ non_existing_flavor = 1234
+ resp, body = self.client.list_servers(dict(flavor=non_existing_flavor))
+ servers = body['servers']
+ self.assertEqual('200', resp['status'])
+ self.assertEqual([], servers)
+
+ @attr(type=['negative', 'gate'])
+ def test_list_servers_by_non_existing_server_name(self):
+ # Listing servers for a non existent server name returns empty list
+ non_existing_name = 'junk_server_1234'
+ resp, body = self.client.list_servers(dict(name=non_existing_name))
+ servers = body['servers']
+ self.assertEqual('200', resp['status'])
+ self.assertEqual([], servers)
+
+ @attr(type=['negative', 'gate'])
+ def test_list_servers_status_non_existing(self):
+ # Return an empty list when invalid status is specified
+ non_existing_status = 'BALONEY'
+ resp, body = self.client.list_servers(dict(status=non_existing_status))
+ servers = body['servers']
+ self.assertEqual('200', resp['status'])
+ self.assertEqual([], servers)
+
+ @attr(type='gate')
+ def test_list_servers_by_limits(self):
+ # List servers by specifying limits
+ resp, body = self.client.list_servers({'limit': 1})
+ self.assertEqual('200', resp['status'])
+ # when _interface='xml', one element for servers_links in servers
+ self.assertEqual(1, len([x for x in body['servers'] if 'id' in x]))
+
+ @attr(type=['negative', 'gate'])
+ def test_list_servers_by_limits_greater_than_actual_count(self):
+ # List servers by specifying a greater value for limit
+ resp, body = self.client.list_servers({'limit': 100})
+ self.assertEqual('200', resp['status'])
+ self.assertEqual(len(self.existing_fixtures), len(body['servers']))
+
+ @attr(type=['negative', 'gate'])
+ def test_list_servers_by_limits_pass_string(self):
+ # Return an error if a string value is passed for limit
+ self.assertRaises(exceptions.BadRequest, self.client.list_servers,
+ {'limit': 'testing'})
+
+ @attr(type=['negative', 'gate'])
+ def test_list_servers_by_limits_pass_negative_value(self):
+ # Return an error if a negative value for limit is passed
+ self.assertRaises(exceptions.BadRequest, self.client.list_servers,
+ {'limit': -1})
+
+ @attr(type='gate')
+ def test_list_servers_by_changes_since(self):
+ # Servers are listed by specifying changes-since date
+ changes_since = {'changes_since': self.start_time.isoformat()}
+ resp, body = self.client.list_servers(changes_since)
+ self.assertEqual('200', resp['status'])
+ # changes-since returns all instances, including deleted.
+ num_expected = (len(self.existing_fixtures) +
+ len(self.deleted_fixtures))
+ self.assertEqual(num_expected, len(body['servers']),
+ "Number of servers %d is wrong in %s" %
+ (num_expected, body['servers']))
+
+ @attr(type=['negative', 'gate'])
+ def test_list_servers_by_changes_since_invalid_date(self):
+ # Return an error when invalid date format is passed
+ self.assertRaises(exceptions.BadRequest, self.client.list_servers,
+ {'changes_since': '2011/01/01'})
+
+ @attr(type=['negative', 'gate'])
+ def test_list_servers_by_changes_since_future_date(self):
+ # Return an empty list when a date in the future is passed
+ changes_since = {'changes_since': '2051-01-01T12:34:00Z'}
+ resp, body = self.client.list_servers(changes_since)
+ self.assertEqual('200', resp['status'])
+ self.assertEqual(0, len(body['servers']))
+
+ @attr(type=['negative', 'gate'])
+ def test_list_servers_detail_server_is_deleted(self):
+ # Server details are not listed for a deleted server
+ deleted_ids = [s['id'] for s in self.deleted_fixtures]
+ resp, body = self.client.list_servers_with_detail()
+ servers = body['servers']
+ actual = [srv for srv in servers
+ if srv['id'] in deleted_ids]
+ self.assertEqual('200', resp['status'])
+ self.assertEqual([], actual)
+
+
+class ListServersNegativeV3TestXML(ListServersNegativeV3TestJSON):
+ _interface = 'xml'
diff --git a/tempest/api/compute/v3/servers/test_server_actions.py b/tempest/api/compute/v3/servers/test_server_actions.py
new file mode 100644
index 0000000..92b9e30
--- /dev/null
+++ b/tempest/api/compute/v3/servers/test_server_actions.py
@@ -0,0 +1,278 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 OpenStack Foundation
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import base64
+import time
+
+import testtools
+
+from tempest.api import compute
+from tempest.api.compute import base
+from tempest.common.utils import data_utils
+from tempest.common.utils.linux.remote_client import RemoteClient
+import tempest.config
+from tempest import exceptions
+from tempest.test import attr
+from tempest.test import skip_because
+
+
+class ServerActionsV3TestJSON(base.BaseV3ComputeTest):
+ _interface = 'json'
+ resize_available = tempest.config.TempestConfig().\
+ compute_feature_enabled.resize
+ run_ssh = tempest.config.TempestConfig().compute.run_ssh
+
+ def setUp(self):
+ # NOTE(afazekas): Normally we use the same server with all test cases,
+ # but if it has an issue, we build a new one
+ super(ServerActionsV3TestJSON, self).setUp()
+ # Check if the server is in a clean state after test
+ try:
+ self.client.wait_for_server_status(self.server_id, 'ACTIVE')
+ except Exception:
+ # Rebuild server if something happened to it during a test
+ self.rebuild_server()
+
+ @classmethod
+ def setUpClass(cls):
+ super(ServerActionsV3TestJSON, cls).setUpClass()
+ cls.client = cls.servers_client
+ cls.rebuild_server()
+
+ @testtools.skipUnless(compute.CHANGE_PASSWORD_AVAILABLE,
+ 'Change password not available.')
+ @attr(type='gate')
+ def test_change_server_password(self):
+ # The server's password should be set to the provided password
+ new_password = 'Newpass1234'
+ resp, body = self.client.change_password(self.server_id, new_password)
+ self.assertEqual(202, resp.status)
+ self.client.wait_for_server_status(self.server_id, 'ACTIVE')
+
+ if self.run_ssh:
+ # Verify that the user can authenticate with the new password
+ resp, server = self.client.get_server(self.server_id)
+ linux_client = RemoteClient(server, self.ssh_user, new_password)
+ self.assertTrue(linux_client.can_authenticate())
+
+ @attr(type='smoke')
+ def test_reboot_server_hard(self):
+ # The server should be power cycled
+ if self.run_ssh:
+ # Get the time the server was last rebooted,
+ 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()
+
+ resp, body = self.client.reboot(self.server_id, 'HARD')
+ self.assertEqual(202, resp.status)
+ self.client.wait_for_server_status(self.server_id, 'ACTIVE')
+
+ if self.run_ssh:
+ # Log in and verify the boot time has changed
+ linux_client = RemoteClient(server, self.ssh_user, self.password)
+ new_boot_time = linux_client.get_boot_time()
+ self.assertGreater(new_boot_time, boot_time)
+
+ @skip_because(bug="1014647")
+ @attr(type='smoke')
+ def test_reboot_server_soft(self):
+ # The server should be signaled to reboot gracefully
+ if self.run_ssh:
+ # Get the time the server was last rebooted,
+ 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()
+
+ resp, body = self.client.reboot(self.server_id, 'SOFT')
+ self.assertEqual(202, resp.status)
+ self.client.wait_for_server_status(self.server_id, 'ACTIVE')
+
+ if self.run_ssh:
+ # Log in and verify the boot time has changed
+ linux_client = RemoteClient(server, self.ssh_user, self.password)
+ new_boot_time = linux_client.get_boot_time()
+ self.assertGreater(new_boot_time, boot_time)
+
+ @attr(type='smoke')
+ def test_rebuild_server(self):
+ # The server should be rebuilt using the provided image and data
+ meta = {'rebuild': 'server'}
+ new_name = data_utils.rand_name('server')
+ file_contents = 'Test server rebuild.'
+ personality = [{'path': 'rebuild.txt',
+ 'contents': base64.b64encode(file_contents)}]
+ password = 'rebuildPassw0rd'
+ resp, rebuilt_server = self.client.rebuild(self.server_id,
+ self.image_ref_alt,
+ name=new_name,
+ metadata=meta,
+ personality=personality,
+ admin_password=password)
+
+ # Verify the properties in the initial response are correct
+ self.assertEqual(self.server_id, rebuilt_server['id'])
+ rebuilt_image_id = rebuilt_server['image']['id']
+ self.assertTrue(self.image_ref_alt.endswith(rebuilt_image_id))
+ self.assertEqual(self.flavor_ref, int(rebuilt_server['flavor']['id']))
+
+ # Verify the server properties after the rebuild completes
+ self.client.wait_for_server_status(rebuilt_server['id'], 'ACTIVE')
+ resp, server = self.client.get_server(rebuilt_server['id'])
+ rebuilt_image_id = rebuilt_server['image']['id']
+ self.assertTrue(self.image_ref_alt.endswith(rebuilt_image_id))
+ self.assertEqual(new_name, rebuilt_server['name'])
+
+ if self.run_ssh:
+ # Verify that the user can authenticate with the provided password
+ linux_client = RemoteClient(server, self.ssh_user, password)
+ self.assertTrue(linux_client.can_authenticate())
+
+ def _detect_server_image_flavor(self, server_id):
+ # Detects the current server image flavor ref.
+ resp, server = self.client.get_server(self.server_id)
+ current_flavor = server['flavor']['id']
+ new_flavor_ref = self.flavor_ref_alt \
+ if int(current_flavor) == self.flavor_ref else self.flavor_ref
+ return int(current_flavor), int(new_flavor_ref)
+
+ @testtools.skipIf(not resize_available, 'Resize not available.')
+ @attr(type='smoke')
+ def test_resize_server_confirm(self):
+ # The server's RAM and disk space should be modified to that of
+ # the provided flavor
+
+ previous_flavor_ref, new_flavor_ref = \
+ self._detect_server_image_flavor(self.server_id)
+
+ resp, server = self.client.resize(self.server_id, new_flavor_ref)
+ self.assertEqual(202, resp.status)
+ self.client.wait_for_server_status(self.server_id, 'VERIFY_RESIZE')
+
+ self.client.confirm_resize(self.server_id)
+ self.client.wait_for_server_status(self.server_id, 'ACTIVE')
+
+ resp, server = self.client.get_server(self.server_id)
+ self.assertEqual(new_flavor_ref, int(server['flavor']['id']))
+
+ @testtools.skipIf(not resize_available, 'Resize not available.')
+ @attr(type='gate')
+ def test_resize_server_revert(self):
+ # The server's RAM and disk space should return to its original
+ # values after a resize is reverted
+
+ previous_flavor_ref, new_flavor_ref = \
+ self._detect_server_image_flavor(self.server_id)
+
+ resp, server = self.client.resize(self.server_id, new_flavor_ref)
+ self.assertEqual(202, resp.status)
+ self.client.wait_for_server_status(self.server_id, 'VERIFY_RESIZE')
+
+ self.client.revert_resize(self.server_id)
+ self.client.wait_for_server_status(self.server_id, 'ACTIVE')
+
+ # Need to poll for the id change until lp#924371 is fixed
+ resp, server = self.client.get_server(self.server_id)
+ start = int(time.time())
+
+ while int(server['flavor']['id']) != previous_flavor_ref:
+ time.sleep(self.build_interval)
+ resp, server = self.client.get_server(self.server_id)
+
+ if int(time.time()) - start >= self.build_timeout:
+ message = 'Server %s failed to revert resize within the \
+ required time (%s s).' % (self.server_id, self.build_timeout)
+ raise exceptions.TimeoutException(message)
+
+ @attr(type='gate')
+ def test_get_console_output(self):
+ # Positive test:Should be able to GET the console output
+ # for a given server_id and number of lines
+ def get_output():
+ resp, output = self.servers_client.get_console_output(
+ self.server_id, 10)
+ self.assertEqual(200, resp.status)
+ self.assertTrue(output, "Console output was empty.")
+ lines = len(output.split('\n'))
+ self.assertEqual(lines, 10)
+ self.wait_for(get_output)
+
+ @skip_because(bug="1014683")
+ @attr(type='gate')
+ def test_get_console_output_server_id_in_reboot_status(self):
+ # Positive test:Should be able to GET the console output
+ # for a given server_id in reboot status
+ resp, output = self.servers_client.reboot(self.server_id, 'SOFT')
+ self.servers_client.wait_for_server_status(self.server_id,
+ 'REBOOT')
+ resp, output = self.servers_client.get_console_output(self.server_id,
+ 10)
+ self.assertEqual(200, resp.status)
+ self.assertIsNotNone(output)
+ lines = len(output.split('\n'))
+ self.assertEqual(lines, 10)
+
+ @attr(type='gate')
+ def test_pause_unpause_server(self):
+ resp, server = self.client.pause_server(self.server_id)
+ self.assertEqual(202, resp.status)
+ self.client.wait_for_server_status(self.server_id, 'PAUSED')
+ resp, server = self.client.unpause_server(self.server_id)
+ self.assertEqual(202, resp.status)
+ self.client.wait_for_server_status(self.server_id, 'ACTIVE')
+
+ @attr(type='gate')
+ def test_suspend_resume_server(self):
+ resp, server = self.client.suspend_server(self.server_id)
+ self.assertEqual(202, resp.status)
+ self.client.wait_for_server_status(self.server_id, 'SUSPENDED')
+ resp, server = self.client.resume_server(self.server_id)
+ self.assertEqual(202, resp.status)
+ self.client.wait_for_server_status(self.server_id, 'ACTIVE')
+
+ @attr(type='gate')
+ def test_stop_start_server(self):
+ resp, server = self.servers_client.stop(self.server_id)
+ self.assertEqual(202, resp.status)
+ self.servers_client.wait_for_server_status(self.server_id, 'SHUTOFF')
+ resp, server = self.servers_client.start(self.server_id)
+ self.assertEqual(202, resp.status)
+ self.servers_client.wait_for_server_status(self.server_id, 'ACTIVE')
+
+ @attr(type='gate')
+ def test_lock_unlock_server(self):
+ # Lock the server,try server stop(exceptions throw),unlock it and retry
+ resp, server = self.servers_client.lock_server(self.server_id)
+ self.assertEqual(202, resp.status)
+ resp, server = self.servers_client.get_server(self.server_id)
+ self.assertEqual(200, resp.status)
+ self.assertEqual(server['status'], 'ACTIVE')
+ # Locked server is not allowed to be stopped by non-admin user
+ self.assertRaises(exceptions.Conflict,
+ self.servers_client.stop, self.server_id)
+ resp, server = self.servers_client.unlock_server(self.server_id)
+ self.assertEqual(202, resp.status)
+ resp, server = self.servers_client.stop(self.server_id)
+ self.assertEqual(202, resp.status)
+ self.servers_client.wait_for_server_status(self.server_id, 'SHUTOFF')
+ resp, server = self.servers_client.start(self.server_id)
+ self.assertEqual(202, resp.status)
+ self.servers_client.wait_for_server_status(self.server_id, 'ACTIVE')
+
+
+class ServerActionsV3TestXML(ServerActionsV3TestJSON):
+ _interface = 'xml'
diff --git a/tempest/api/compute/v3/servers/test_server_addresses.py b/tempest/api/compute/v3/servers/test_server_addresses.py
new file mode 100644
index 0000000..82588b6
--- /dev/null
+++ b/tempest/api/compute/v3/servers/test_server_addresses.py
@@ -0,0 +1,84 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 OpenStack Foundation
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.api.compute import base
+from tempest import exceptions
+from tempest.test import attr
+
+
+class ServerAddressesV3Test(base.BaseV3ComputeTest):
+ _interface = 'json'
+
+ @classmethod
+ def setUpClass(cls):
+ super(ServerAddressesV3Test, cls).setUpClass()
+ cls.client = cls.servers_client
+
+ resp, cls.server = cls.create_test_server(wait_until='ACTIVE')
+
+ @attr(type=['negative', 'gate'])
+ def test_list_server_addresses_invalid_server_id(self):
+ # List addresses request should fail if server id not in system
+ self.assertRaises(exceptions.NotFound, self.client.list_addresses,
+ '999')
+
+ @attr(type=['negative', 'gate'])
+ def test_list_server_addresses_by_network_neg(self):
+ # List addresses by network should fail if network name not valid
+ self.assertRaises(exceptions.NotFound,
+ self.client.list_addresses_by_network,
+ self.server['id'], 'invalid')
+
+ @attr(type='smoke')
+ def test_list_server_addresses(self):
+ # All public and private addresses for
+ # a server should be returned
+
+ resp, addresses = self.client.list_addresses(self.server['id'])
+ self.assertEqual('200', resp['status'])
+
+ # We do not know the exact network configuration, but an instance
+ # should at least have a single public or private address
+ self.assertTrue(len(addresses) >= 1)
+ for network_name, network_addresses in addresses.iteritems():
+ self.assertTrue(len(network_addresses) >= 1)
+ for address in network_addresses:
+ self.assertTrue(address['addr'])
+ self.assertTrue(address['version'])
+
+ @attr(type='smoke')
+ def test_list_server_addresses_by_network(self):
+ # Providing a network type should filter
+ # the addresses return by that type
+
+ resp, addresses = self.client.list_addresses(self.server['id'])
+
+ # Once again we don't know the environment's exact network config,
+ # but the response for each individual network should be the same
+ # as the partial result of the full address list
+ id = self.server['id']
+ for addr_type in addresses:
+ resp, addr = self.client.list_addresses_by_network(id, addr_type)
+ self.assertEqual('200', resp['status'])
+
+ addr = addr[addr_type]
+ for address in addresses[addr_type]:
+ self.assertTrue(any([a for a in addr if a == address]))
+
+
+class ServerAddressesV3TestXML(ServerAddressesV3Test):
+ _interface = 'xml'
diff --git a/tempest/api/compute/v3/test_extensions.py b/tempest/api/compute/v3/test_extensions.py
new file mode 100644
index 0000000..d7269d1
--- /dev/null
+++ b/tempest/api/compute/v3/test_extensions.py
@@ -0,0 +1,35 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 OpenStack Foundation
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+
+from tempest.api.compute import base
+from tempest.test import attr
+
+
+class ExtensionsV3TestJSON(base.BaseV3ComputeTest):
+ _interface = 'json'
+
+ @attr(type='gate')
+ def test_list_extensions(self):
+ # List of all extensions
+ resp, extensions = self.extensions_client.list_extensions()
+ self.assertIn("extensions", extensions)
+ self.assertEqual(200, resp.status)
+
+
+class ExtensionsV3TestXML(ExtensionsV3TestJSON):
+ _interface = 'xml'
diff --git a/tempest/api/compute/volumes/test_attach_volume.py b/tempest/api/compute/volumes/test_attach_volume.py
index ee1ad9e..660de95 100644
--- a/tempest/api/compute/volumes/test_attach_volume.py
+++ b/tempest/api/compute/volumes/test_attach_volume.py
@@ -23,7 +23,7 @@
from tempest.test import attr
-class AttachVolumeTestJSON(base.BaseComputeTest):
+class AttachVolumeTestJSON(base.BaseV2ComputeTest):
_interface = 'json'
run_ssh = tempest.config.TempestConfig().compute.run_ssh
@@ -53,8 +53,9 @@
def _create_and_attach(self):
# Start a server and wait for it to become ready
- resp, server = self.create_server(wait_until='ACTIVE',
- adminPass=self.image_ssh_password)
+ admin_pass = self.image_ssh_password
+ resp, server = self.create_test_server(wait_until='ACTIVE',
+ adminPass=admin_pass)
self.server = server
# Record addresses so that we can ssh later
diff --git a/tempest/api/compute/volumes/test_volumes_get.py b/tempest/api/compute/volumes/test_volumes_get.py
index fba8347..ae6996d 100644
--- a/tempest/api/compute/volumes/test_volumes_get.py
+++ b/tempest/api/compute/volumes/test_volumes_get.py
@@ -16,11 +16,11 @@
# under the License.
from tempest.api.compute import base
-from tempest.common.utils.data_utils import rand_name
+from tempest.common.utils import data_utils
from tempest.test import attr
-class VolumesGetTestJSON(base.BaseComputeTest):
+class VolumesGetTestJSON(base.BaseV2ComputeTest):
_interface = 'json'
@@ -36,7 +36,7 @@
def test_volume_create_get_delete(self):
# CREATE, GET, DELETE Volume
volume = None
- v_name = rand_name('Volume-%s-') % self._interface
+ v_name = data_utils.rand_name('Volume-%s-') % self._interface
metadata = {'Type': 'work'}
# Create volume
resp, volume = self.client.create_volume(size=1,
@@ -73,7 +73,7 @@
@attr(type='gate')
def test_volume_get_metadata_none(self):
# CREATE, GET empty metadata dict
- v_name = rand_name('Volume-')
+ v_name = data_utils.rand_name('Volume-')
# Create volume
resp, volume = self.client.create_volume(size=1,
display_name=v_name,
diff --git a/tempest/api/compute/volumes/test_volumes_list.py b/tempest/api/compute/volumes/test_volumes_list.py
index 956abdf..f214641 100644
--- a/tempest/api/compute/volumes/test_volumes_list.py
+++ b/tempest/api/compute/volumes/test_volumes_list.py
@@ -16,11 +16,11 @@
# under the License.
from tempest.api.compute import base
-from tempest.common.utils.data_utils import rand_name
+from tempest.common.utils import data_utils
from tempest.test import attr
-class VolumesTestJSON(base.BaseComputeTest):
+class VolumesTestJSON(base.BaseV2ComputeTest):
"""
This test creates a number of 1G volumes. To run successfully,
@@ -43,7 +43,7 @@
cls.volume_list = []
cls.volume_id_list = []
for i in range(3):
- v_name = rand_name('volume-%s')
+ v_name = data_utils.rand_name('volume-%s')
metadata = {'Type': 'work'}
v_name += cls._interface
try:
diff --git a/tempest/api/compute/volumes/test_volumes_negative.py b/tempest/api/compute/volumes/test_volumes_negative.py
index 9ca1380..785902e 100644
--- a/tempest/api/compute/volumes/test_volumes_negative.py
+++ b/tempest/api/compute/volumes/test_volumes_negative.py
@@ -15,13 +15,15 @@
# License for the specific language governing permissions and limitations
# under the License.
+import uuid
+
from tempest.api.compute import base
-from tempest.common.utils.data_utils import rand_name
+from tempest.common.utils import data_utils
from tempest import exceptions
from tempest.test import attr
-class VolumesNegativeTest(base.BaseComputeTest):
+class VolumesNegativeTest(base.BaseV2ComputeTest):
_interface = 'json'
@classmethod
@@ -36,39 +38,23 @@
def test_volume_get_nonexistant_volume_id(self):
# Negative: Should not be able to get details of nonexistant volume
# Creating a nonexistant volume id
- volume_id_list = list()
- resp, body = self.client.list_volumes()
- for i in range(len(body)):
- volume_id_list.append(body[i]['id'])
- while True:
- non_exist_id = rand_name('999')
- if non_exist_id not in volume_id_list:
- break
# Trying to GET a non existant volume
self.assertRaises(exceptions.NotFound, self.client.get_volume,
- non_exist_id)
+ str(uuid.uuid4()))
@attr(type=['negative', 'gate'])
def test_volume_delete_nonexistant_volume_id(self):
# Negative: Should not be able to delete nonexistant Volume
# Creating nonexistant volume id
- volume_id_list = list()
- resp, body = self.client.list_volumes()
- for i in range(len(body)):
- volume_id_list.append(body[i]['id'])
- while True:
- non_exist_id = rand_name('999')
- if non_exist_id not in volume_id_list:
- break
# Trying to DELETE a non existant volume
self.assertRaises(exceptions.NotFound, self.client.delete_volume,
- non_exist_id)
+ str(uuid.uuid4()))
@attr(type=['negative', 'gate'])
def test_create_volume_with_invalid_size(self):
# Negative: Should not be able to create volume with invalid size
# in request
- v_name = rand_name('Volume-')
+ v_name = data_utils.rand_name('Volume-')
metadata = {'Type': 'work'}
self.assertRaises(exceptions.BadRequest, self.client.create_volume,
size='#$%', display_name=v_name, metadata=metadata)
@@ -77,7 +63,7 @@
def test_create_volume_with_out_passing_size(self):
# Negative: Should not be able to create volume without passing size
# in request
- v_name = rand_name('Volume-')
+ v_name = data_utils.rand_name('Volume-')
metadata = {'Type': 'work'}
self.assertRaises(exceptions.BadRequest, self.client.create_volume,
size='', display_name=v_name, metadata=metadata)
@@ -85,7 +71,7 @@
@attr(type=['negative', 'gate'])
def test_create_volume_with_size_zero(self):
# Negative: Should not be able to create volume with size zero
- v_name = rand_name('Volume-')
+ v_name = data_utils.rand_name('Volume-')
metadata = {'Type': 'work'}
self.assertRaises(exceptions.BadRequest, self.client.create_volume,
size='0', display_name=v_name, metadata=metadata)
diff --git a/tempest/api/identity/admin/test_roles.py b/tempest/api/identity/admin/test_roles.py
index 690d14f..8205d15 100644
--- a/tempest/api/identity/admin/test_roles.py
+++ b/tempest/api/identity/admin/test_roles.py
@@ -16,8 +16,7 @@
# under the License.
from tempest.api.identity import base
-from tempest.common.utils.data_utils import rand_name
-from tempest import exceptions
+from tempest.common.utils import data_utils
from tempest.test import attr
@@ -28,7 +27,8 @@
def setUpClass(cls):
super(RolesTestJSON, cls).setUpClass()
for _ in xrange(5):
- resp, role = cls.client.create_role(rand_name('role-'))
+ role_name = data_utils.rand_name(name='role-')
+ resp, role = cls.client.create_role(role_name)
cls.data.roles.append(role)
def _get_role_params(self):
@@ -55,23 +55,9 @@
self.assertEqual(len(found), len(self.data.roles))
@attr(type='gate')
- def test_list_roles_by_unauthorized_user(self):
- # Non-administrator user should not be able to list roles
- self.assertRaises(exceptions.Unauthorized,
- self.non_admin_client.list_roles)
-
- @attr(type='gate')
- def test_list_roles_request_without_token(self):
- # Request to list roles without a valid token should fail
- token = self.client.get_auth()
- self.client.delete_token(token)
- self.assertRaises(exceptions.Unauthorized, self.client.list_roles)
- self.client.clear_auth()
-
- @attr(type='gate')
def test_role_create_delete(self):
# Role should be created, verified, and deleted
- role_name = rand_name('role-test-')
+ role_name = data_utils.rand_name(name='role-test-')
resp, body = self.client.create_role(role_name)
self.assertIn('status', resp)
self.assertTrue(resp['status'].startswith('2'))
@@ -90,23 +76,6 @@
self.assertFalse(any(found))
@attr(type='gate')
- def test_role_create_blank_name(self):
- # Should not be able to create a role with a blank name
- self.assertRaises(exceptions.BadRequest, self.client.create_role, '')
-
- @attr(type='gate')
- def test_role_create_duplicate(self):
- # Role names should be unique
- role_name = rand_name('role-dup-')
- resp, body = self.client.create_role(role_name)
- role1_id = body.get('id')
- self.assertIn('status', resp)
- self.assertTrue(resp['status'].startswith('2'))
- self.addCleanup(self.client.delete_role, role1_id)
- self.assertRaises(exceptions.Duplicate, self.client.create_role,
- role_name)
-
- @attr(type='gate')
def test_assign_user_role(self):
# Assign a role to a user on a tenant
(user, tenant, role) = self._get_role_params()
@@ -115,55 +84,6 @@
self.assert_role_in_role_list(role, roles)
@attr(type='gate')
- def test_assign_user_role_by_unauthorized_user(self):
- # Non-administrator user should not be authorized to
- # assign a role to user
- (user, tenant, role) = self._get_role_params()
- self.assertRaises(exceptions.Unauthorized,
- self.non_admin_client.assign_user_role,
- tenant['id'], user['id'], role['id'])
-
- @attr(type='gate')
- def test_assign_user_role_request_without_token(self):
- # Request to assign a role to a user without a valid token
- (user, tenant, role) = self._get_role_params()
- token = self.client.get_auth()
- self.client.delete_token(token)
- self.assertRaises(exceptions.Unauthorized,
- self.client.assign_user_role, tenant['id'],
- user['id'], role['id'])
- self.client.clear_auth()
-
- @attr(type='gate')
- def test_assign_user_role_for_non_existent_user(self):
- # Attempt to assign a role to a non existent user should fail
- (user, tenant, role) = self._get_role_params()
- self.assertRaises(exceptions.NotFound, self.client.assign_user_role,
- tenant['id'], 'junk-user-id-999', role['id'])
-
- @attr(type='gate')
- def test_assign_user_role_for_non_existent_role(self):
- # Attempt to assign a non existent role to user should fail
- (user, tenant, role) = self._get_role_params()
- self.assertRaises(exceptions.NotFound, self.client.assign_user_role,
- tenant['id'], user['id'], 'junk-role-id-12345')
-
- @attr(type='gate')
- def test_assign_user_role_for_non_existent_tenant(self):
- # Attempt to assign a role on a non existent tenant should fail
- (user, tenant, role) = self._get_role_params()
- self.assertRaises(exceptions.NotFound, self.client.assign_user_role,
- 'junk-tenant-1234', user['id'], role['id'])
-
- @attr(type='gate')
- def test_assign_duplicate_user_role(self):
- # Duplicate user role should not get assigned
- (user, tenant, role) = self._get_role_params()
- self.client.assign_user_role(tenant['id'], user['id'], role['id'])
- self.assertRaises(exceptions.Duplicate, self.client.assign_user_role,
- tenant['id'], user['id'], role['id'])
-
- @attr(type='gate')
def test_remove_user_role(self):
# Remove a role assigned to a user on a tenant
(user, tenant, role) = self._get_role_params()
@@ -174,62 +94,6 @@
self.assertEqual(resp['status'], '204')
@attr(type='gate')
- def test_remove_user_role_by_unauthorized_user(self):
- # Non-administrator user should not be authorized to
- # remove a user's role
- (user, tenant, role) = self._get_role_params()
- resp, user_role = self.client.assign_user_role(tenant['id'],
- user['id'],
- role['id'])
- self.assertRaises(exceptions.Unauthorized,
- self.non_admin_client.remove_user_role,
- tenant['id'], user['id'], role['id'])
-
- @attr(type='gate')
- def test_remove_user_role_request_without_token(self):
- # Request to remove a user's role without a valid token
- (user, tenant, role) = self._get_role_params()
- resp, user_role = self.client.assign_user_role(tenant['id'],
- user['id'],
- role['id'])
- token = self.client.get_auth()
- self.client.delete_token(token)
- self.assertRaises(exceptions.Unauthorized,
- self.client.remove_user_role, tenant['id'],
- user['id'], role['id'])
- self.client.clear_auth()
-
- @attr(type='gate')
- def test_remove_user_role_non_existant_user(self):
- # Attempt to remove a role from a non existent user should fail
- (user, tenant, role) = self._get_role_params()
- resp, user_role = self.client.assign_user_role(tenant['id'],
- user['id'],
- role['id'])
- self.assertRaises(exceptions.NotFound, self.client.remove_user_role,
- tenant['id'], 'junk-user-id-123', role['id'])
-
- @attr(type='gate')
- def test_remove_user_role_non_existant_role(self):
- # Attempt to delete a non existent role from a user should fail
- (user, tenant, role) = self._get_role_params()
- resp, user_role = self.client.assign_user_role(tenant['id'],
- user['id'],
- role['id'])
- self.assertRaises(exceptions.NotFound, self.client.remove_user_role,
- tenant['id'], user['id'], 'junk-user-role-123')
-
- @attr(type='gate')
- def test_remove_user_role_non_existant_tenant(self):
- # Attempt to remove a role from a non existent tenant should fail
- (user, tenant, role) = self._get_role_params()
- resp, user_role = self.client.assign_user_role(tenant['id'],
- user['id'],
- role['id'])
- self.assertRaises(exceptions.NotFound, self.client.remove_user_role,
- 'junk-tenant-id-123', user['id'], role['id'])
-
- @attr(type='gate')
def test_list_user_roles(self):
# List roles assigned to a user on tenant
(user, tenant, role) = self._get_role_params()
@@ -237,36 +101,6 @@
resp, roles = self.client.list_user_roles(tenant['id'], user['id'])
self.assert_role_in_role_list(role, roles)
- @attr(type='gate')
- def test_list_user_roles_by_unauthorized_user(self):
- # Non-administrator user should not be authorized to list
- # a user's roles
- (user, tenant, role) = self._get_role_params()
- self.client.assign_user_role(tenant['id'], user['id'], role['id'])
- self.assertRaises(exceptions.Unauthorized,
- self.non_admin_client.list_user_roles, tenant['id'],
- user['id'])
-
- @attr(type='gate')
- def test_list_user_roles_request_without_token(self):
- # Request to list user's roles without a valid token should fail
- (user, tenant, role) = self._get_role_params()
- token = self.client.get_auth()
- self.client.delete_token(token)
- try:
- self.assertRaises(exceptions.Unauthorized,
- self.client.list_user_roles, tenant['id'],
- user['id'])
- finally:
- self.client.clear_auth()
-
- @attr(type='gate')
- def test_list_user_roles_for_non_existent_user(self):
- # Attempt to list roles of a non existent user should fail
- (user, tenant, role) = self._get_role_params()
- self.assertRaises(exceptions.NotFound, self.client.list_user_roles,
- tenant['id'], 'junk-role-aabbcc11')
-
class RolesTestXML(RolesTestJSON):
_interface = 'xml'
diff --git a/tempest/api/identity/admin/test_roles_negative.py b/tempest/api/identity/admin/test_roles_negative.py
new file mode 100644
index 0000000..83d1d4d
--- /dev/null
+++ b/tempest/api/identity/admin/test_roles_negative.py
@@ -0,0 +1,262 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 Huawei Technologies Co.,LTD.
+# 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 uuid
+
+from tempest.api.identity import base
+from tempest.common.utils import data_utils
+from tempest import exceptions
+from tempest.test import attr
+
+
+class RolesNegativeTestJSON(base.BaseIdentityAdminTest):
+ _interface = 'json'
+
+ def _get_role_params(self):
+ self.data.setup_test_user()
+ self.data.setup_test_role()
+ user = self.get_user_by_name(self.data.test_user)
+ tenant = self.get_tenant_by_name(self.data.test_tenant)
+ role = self.get_role_by_name(self.data.test_role)
+ return (user, tenant, role)
+
+ @attr(type=['negative', 'gate'])
+ def test_list_roles_by_unauthorized_user(self):
+ # Non-administrator user should not be able to list roles
+ self.assertRaises(exceptions.Unauthorized,
+ self.non_admin_client.list_roles)
+
+ @attr(type=['negative', 'gate'])
+ def test_list_roles_request_without_token(self):
+ # Request to list roles without a valid token should fail
+ token = self.client.get_auth()
+ self.client.delete_token(token)
+ self.assertRaises(exceptions.Unauthorized, self.client.list_roles)
+ self.client.clear_auth()
+
+ @attr(type=['negative', 'gate'])
+ def test_role_create_blank_name(self):
+ # Should not be able to create a role with a blank name
+ self.assertRaises(exceptions.BadRequest, self.client.create_role, '')
+
+ @attr(type=['negative', 'gate'])
+ def test_create_role_by_unauthorized_user(self):
+ # Non-administrator user should not be able to create role
+ role_name = data_utils.rand_name(name='role-')
+ self.assertRaises(exceptions.Unauthorized,
+ self.non_admin_client.create_role, role_name)
+
+ @attr(type=['negative', 'gate'])
+ def test_create_role_request_without_token(self):
+ # Request to create role without a valid token should fail
+ token = self.client.get_auth()
+ self.client.delete_token(token)
+ role_name = data_utils.rand_name(name='role-')
+ self.assertRaises(exceptions.Unauthorized,
+ self.client.create_role, role_name)
+ self.client.clear_auth()
+
+ @attr(type=['negative', 'gate'])
+ def test_role_create_duplicate(self):
+ # Role names should be unique
+ role_name = data_utils.rand_name(name='role-dup-')
+ resp, body = self.client.create_role(role_name)
+ role1_id = body.get('id')
+ self.assertIn('status', resp)
+ self.assertTrue(resp['status'].startswith('2'))
+ self.addCleanup(self.client.delete_role, role1_id)
+ self.assertRaises(exceptions.Conflict, self.client.create_role,
+ role_name)
+
+ @attr(type=['negative', 'gate'])
+ def test_delete_role_by_unauthorized_user(self):
+ # Non-administrator user should not be able to delete role
+ role_name = data_utils.rand_name(name='role-')
+ resp, body = self.client.create_role(role_name)
+ self.assertEqual(200, resp.status)
+ self.data.roles.append(body)
+ role_id = body.get('id')
+ self.assertRaises(exceptions.Unauthorized,
+ self.non_admin_client.delete_role, role_id)
+
+ @attr(type=['negative', 'gate'])
+ def test_delete_role_request_without_token(self):
+ # Request to delete role without a valid token should fail
+ role_name = data_utils.rand_name(name='role-')
+ resp, body = self.client.create_role(role_name)
+ self.assertEqual(200, resp.status)
+ self.data.roles.append(body)
+ role_id = body.get('id')
+ token = self.client.get_auth()
+ self.client.delete_token(token)
+ self.assertRaises(exceptions.Unauthorized,
+ self.client.delete_role,
+ role_id)
+ self.client.clear_auth()
+
+ @attr(type=['negative', 'gate'])
+ def test_delete_role_non_existent(self):
+ # Attempt to delete a non existent role should fail
+ non_existent_role = str(uuid.uuid4().hex)
+ self.assertRaises(exceptions.NotFound, self.client.delete_role,
+ non_existent_role)
+
+ @attr(type=['negative', 'gate'])
+ def test_assign_user_role_by_unauthorized_user(self):
+ # Non-administrator user should not be authorized to
+ # assign a role to user
+ (user, tenant, role) = self._get_role_params()
+ self.assertRaises(exceptions.Unauthorized,
+ self.non_admin_client.assign_user_role,
+ tenant['id'], user['id'], role['id'])
+
+ @attr(type=['negative', 'gate'])
+ def test_assign_user_role_request_without_token(self):
+ # Request to assign a role to a user without a valid token
+ (user, tenant, role) = self._get_role_params()
+ token = self.client.get_auth()
+ self.client.delete_token(token)
+ self.assertRaises(exceptions.Unauthorized,
+ self.client.assign_user_role, tenant['id'],
+ user['id'], role['id'])
+ self.client.clear_auth()
+
+ @attr(type=['negative', 'gate'])
+ def test_assign_user_role_for_non_existent_user(self):
+ # Attempt to assign a role to a non existent user should fail
+ (user, tenant, role) = self._get_role_params()
+ non_existent_user = str(uuid.uuid4().hex)
+ self.assertRaises(exceptions.NotFound, self.client.assign_user_role,
+ tenant['id'], non_existent_user, role['id'])
+
+ @attr(type=['negative', 'gate'])
+ def test_assign_user_role_for_non_existent_role(self):
+ # Attempt to assign a non existent role to user should fail
+ (user, tenant, role) = self._get_role_params()
+ non_existent_role = str(uuid.uuid4().hex)
+ self.assertRaises(exceptions.NotFound, self.client.assign_user_role,
+ tenant['id'], user['id'], non_existent_role)
+
+ @attr(type=['negative', 'gate'])
+ def test_assign_user_role_for_non_existent_tenant(self):
+ # Attempt to assign a role on a non existent tenant should fail
+ (user, tenant, role) = self._get_role_params()
+ non_existent_tenant = str(uuid.uuid4().hex)
+ self.assertRaises(exceptions.NotFound, self.client.assign_user_role,
+ non_existent_tenant, user['id'], role['id'])
+
+ @attr(type=['negative', 'gate'])
+ def test_assign_duplicate_user_role(self):
+ # Duplicate user role should not get assigned
+ (user, tenant, role) = self._get_role_params()
+ self.client.assign_user_role(tenant['id'], user['id'], role['id'])
+ self.assertRaises(exceptions.Conflict, self.client.assign_user_role,
+ tenant['id'], user['id'], role['id'])
+
+ @attr(type=['negative', 'gate'])
+ def test_remove_user_role_by_unauthorized_user(self):
+ # Non-administrator user should not be authorized to
+ # remove a user's role
+ (user, tenant, role) = self._get_role_params()
+ resp, user_role = self.client.assign_user_role(tenant['id'],
+ user['id'],
+ role['id'])
+ self.assertRaises(exceptions.Unauthorized,
+ self.non_admin_client.remove_user_role,
+ tenant['id'], user['id'], role['id'])
+
+ @attr(type=['negative', 'gate'])
+ def test_remove_user_role_request_without_token(self):
+ # Request to remove a user's role without a valid token
+ (user, tenant, role) = self._get_role_params()
+ resp, user_role = self.client.assign_user_role(tenant['id'],
+ user['id'],
+ role['id'])
+ token = self.client.get_auth()
+ self.client.delete_token(token)
+ self.assertRaises(exceptions.Unauthorized,
+ self.client.remove_user_role, tenant['id'],
+ user['id'], role['id'])
+ self.client.clear_auth()
+
+ @attr(type=['negative', 'gate'])
+ def test_remove_user_role_non_existant_user(self):
+ # Attempt to remove a role from a non existent user should fail
+ (user, tenant, role) = self._get_role_params()
+ resp, user_role = self.client.assign_user_role(tenant['id'],
+ user['id'],
+ role['id'])
+ non_existant_user = str(uuid.uuid4().hex)
+ self.assertRaises(exceptions.NotFound, self.client.remove_user_role,
+ tenant['id'], non_existant_user, role['id'])
+
+ @attr(type=['negative', 'gate'])
+ def test_remove_user_role_non_existant_role(self):
+ # Attempt to delete a non existent role from a user should fail
+ (user, tenant, role) = self._get_role_params()
+ resp, user_role = self.client.assign_user_role(tenant['id'],
+ user['id'],
+ role['id'])
+ non_existant_role = str(uuid.uuid4().hex)
+ self.assertRaises(exceptions.NotFound, self.client.remove_user_role,
+ tenant['id'], user['id'], non_existant_role)
+
+ @attr(type=['negative', 'gate'])
+ def test_remove_user_role_non_existant_tenant(self):
+ # Attempt to remove a role from a non existent tenant should fail
+ (user, tenant, role) = self._get_role_params()
+ resp, user_role = self.client.assign_user_role(tenant['id'],
+ user['id'],
+ role['id'])
+ non_existant_tenant = str(uuid.uuid4().hex)
+ self.assertRaises(exceptions.NotFound, self.client.remove_user_role,
+ non_existant_tenant, user['id'], role['id'])
+
+ @attr(type=['negative', 'gate'])
+ def test_list_user_roles_by_unauthorized_user(self):
+ # Non-administrator user should not be authorized to list
+ # a user's roles
+ (user, tenant, role) = self._get_role_params()
+ self.client.assign_user_role(tenant['id'], user['id'], role['id'])
+ self.assertRaises(exceptions.Unauthorized,
+ self.non_admin_client.list_user_roles, tenant['id'],
+ user['id'])
+
+ @attr(type=['negative', 'gate'])
+ def test_list_user_roles_request_without_token(self):
+ # Request to list user's roles without a valid token should fail
+ (user, tenant, role) = self._get_role_params()
+ token = self.client.get_auth()
+ self.client.delete_token(token)
+ try:
+ self.assertRaises(exceptions.Unauthorized,
+ self.client.list_user_roles, tenant['id'],
+ user['id'])
+ finally:
+ self.client.clear_auth()
+
+ @attr(type=['negative', 'gate'])
+ def test_list_user_roles_for_non_existent_user(self):
+ # Attempt to list roles of a non existent user should fail
+ (user, tenant, role) = self._get_role_params()
+ non_existent_user = str(uuid.uuid4().hex)
+ self.assertRaises(exceptions.NotFound, self.client.list_user_roles,
+ tenant['id'], non_existent_user)
+
+
+class RolesTestXML(RolesNegativeTestJSON):
+ _interface = 'xml'
diff --git a/tempest/api/identity/admin/test_services.py b/tempest/api/identity/admin/test_services.py
index df6c317..7fe5171 100644
--- a/tempest/api/identity/admin/test_services.py
+++ b/tempest/api/identity/admin/test_services.py
@@ -17,7 +17,7 @@
from tempest.api.identity import base
-from tempest.common.utils.data_utils import rand_name
+from tempest.common.utils import data_utils
from tempest import exceptions
from tempest.test import attr
@@ -30,9 +30,9 @@
# GET Service
try:
# Creating a Service
- name = rand_name('service-')
- type = rand_name('type--')
- description = rand_name('description-')
+ name = data_utils.rand_name('service-')
+ type = data_utils.rand_name('type--')
+ description = data_utils.rand_name('description-')
resp, service_data = self.client.create_service(
name, type, description=description)
self.assertTrue(resp['status'].startswith('2'))
@@ -72,9 +72,9 @@
# Create, List, Verify and Delete Services
services = []
for _ in xrange(3):
- name = rand_name('service-')
- type = rand_name('type--')
- description = rand_name('description-')
+ name = data_utils.rand_name('service-')
+ type = data_utils.rand_name('type--')
+ description = data_utils.rand_name('description-')
resp, service = self.client.create_service(
name, type, description=description)
services.append(service)
diff --git a/tempest/api/identity/admin/test_tenant_negative.py b/tempest/api/identity/admin/test_tenant_negative.py
new file mode 100644
index 0000000..d10080b
--- /dev/null
+++ b/tempest/api/identity/admin/test_tenant_negative.py
@@ -0,0 +1,148 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 Huawei Technologies Co.,LTD.
+# 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 uuid
+
+from tempest.api.identity import base
+from tempest.common.utils import data_utils
+from tempest import exceptions
+from tempest.test import attr
+
+
+class TenantsNegativeTestJSON(base.BaseIdentityAdminTest):
+ _interface = 'json'
+
+ @attr(type=['negative', 'gate'])
+ def test_list_tenants_by_unauthorized_user(self):
+ # Non-administrator user should not be able to list tenants
+ self.assertRaises(exceptions.Unauthorized,
+ self.non_admin_client.list_tenants)
+
+ @attr(type=['negative', 'gate'])
+ def test_list_tenant_request_without_token(self):
+ # Request to list tenants without a valid token should fail
+ token = self.client.get_auth()
+ self.client.delete_token(token)
+ self.assertRaises(exceptions.Unauthorized, self.client.list_tenants)
+ self.client.clear_auth()
+
+ @attr(type=['negative', 'gate'])
+ def test_tenant_delete_by_unauthorized_user(self):
+ # Non-administrator user should not be able to delete a tenant
+ tenant_name = data_utils.rand_name(name='tenant-')
+ resp, tenant = self.client.create_tenant(tenant_name)
+ self.assertEqual(200, resp.status)
+ self.data.tenants.append(tenant)
+ self.assertRaises(exceptions.Unauthorized,
+ self.non_admin_client.delete_tenant, tenant['id'])
+
+ @attr(type=['negative', 'gate'])
+ def test_tenant_delete_request_without_token(self):
+ # Request to delete a tenant without a valid token should fail
+ tenant_name = data_utils.rand_name(name='tenant-')
+ resp, tenant = self.client.create_tenant(tenant_name)
+ self.assertEqual(200, resp.status)
+ 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', 'gate'])
+ def test_delete_non_existent_tenant(self):
+ # Attempt to delete a non existent tenant should fail
+ self.assertRaises(exceptions.NotFound, self.client.delete_tenant,
+ str(uuid.uuid4().hex))
+
+ @attr(type=['negative', 'gate'])
+ def test_tenant_create_duplicate(self):
+ # Tenant names should be unique
+ tenant_name = data_utils.rand_name(name='tenant-')
+ resp, body = self.client.create_tenant(tenant_name)
+ self.assertEqual(200, resp.status)
+ tenant = body
+ self.data.tenants.append(tenant)
+ tenant1_id = body.get('id')
+
+ self.addCleanup(self.client.delete_tenant, tenant1_id)
+ self.addCleanup(self.data.tenants.remove, tenant)
+ self.assertRaises(exceptions.Conflict, self.client.create_tenant,
+ tenant_name)
+
+ @attr(type=['negative', 'gate'])
+ def test_create_tenant_by_unauthorized_user(self):
+ # Non-administrator user should not be authorized to create a tenant
+ tenant_name = data_utils.rand_name(name='tenant-')
+ self.assertRaises(exceptions.Unauthorized,
+ self.non_admin_client.create_tenant, tenant_name)
+
+ @attr(type=['negative', 'gate'])
+ def test_create_tenant_request_without_token(self):
+ # Create tenant request without a token should not be authorized
+ tenant_name = data_utils.rand_name(name='tenant-')
+ token = self.client.get_auth()
+ self.client.delete_token(token)
+ self.assertRaises(exceptions.Unauthorized, self.client.create_tenant,
+ tenant_name)
+ self.client.clear_auth()
+
+ @attr(type=['negative', 'gate'])
+ def test_create_tenant_with_empty_name(self):
+ # Tenant name should not be empty
+ self.assertRaises(exceptions.BadRequest, self.client.create_tenant,
+ name='')
+
+ @attr(type=['negative', 'gate'])
+ def test_create_tenants_name_length_over_64(self):
+ # Tenant name length should not be greater than 64 characters
+ tenant_name = 'a' * 65
+ self.assertRaises(exceptions.BadRequest, self.client.create_tenant,
+ tenant_name)
+
+ @attr(type=['negative', 'gate'])
+ def test_update_non_existent_tenant(self):
+ # Attempt to update a non existent tenant should fail
+ self.assertRaises(exceptions.NotFound, self.client.update_tenant,
+ str(uuid.uuid4().hex))
+
+ @attr(type=['negative', 'gate'])
+ def test_tenant_update_by_unauthorized_user(self):
+ # Non-administrator user should not be able to update a tenant
+ tenant_name = data_utils.rand_name(name='tenant-')
+ resp, tenant = self.client.create_tenant(tenant_name)
+ self.assertEqual(200, resp.status)
+ self.data.tenants.append(tenant)
+ self.assertRaises(exceptions.Unauthorized,
+ self.non_admin_client.update_tenant, tenant['id'])
+
+ @attr(type=['negative', 'gate'])
+ def test_tenant_update_request_without_token(self):
+ # Request to update a tenant without a valid token should fail
+ tenant_name = data_utils.rand_name(name='tenant-')
+ resp, tenant = self.client.create_tenant(tenant_name)
+ self.assertEqual(200, resp.status)
+ self.data.tenants.append(tenant)
+ token = self.client.get_auth()
+ self.client.delete_token(token)
+ self.assertRaises(exceptions.Unauthorized, self.client.update_tenant,
+ tenant['id'])
+ self.client.clear_auth()
+
+
+class TenantsNegativeTestXML(TenantsNegativeTestJSON):
+ _interface = 'xml'
diff --git a/tempest/api/identity/admin/test_tenants.py b/tempest/api/identity/admin/test_tenants.py
index 486b739..e36b543 100644
--- a/tempest/api/identity/admin/test_tenants.py
+++ b/tempest/api/identity/admin/test_tenants.py
@@ -16,8 +16,7 @@
# under the License.
from tempest.api.identity import base
-from tempest.common.utils.data_utils import rand_name
-from tempest import exceptions
+from tempest.common.utils import data_utils
from tempest.test import attr
@@ -25,25 +24,13 @@
_interface = 'json'
@attr(type='gate')
- def test_list_tenants_by_unauthorized_user(self):
- # Non-administrator user should not be able to list tenants
- self.assertRaises(exceptions.Unauthorized,
- self.non_admin_client.list_tenants)
-
- @attr(type='gate')
- def test_list_tenant_request_without_token(self):
- # Request to list tenants without a valid token should fail
- token = self.client.get_auth()
- self.client.delete_token(token)
- self.assertRaises(exceptions.Unauthorized, self.client.list_tenants)
- self.client.clear_auth()
-
- @attr(type='gate')
def test_tenant_list_delete(self):
# Create several tenants and delete them
tenants = []
for _ in xrange(3):
- resp, tenant = self.client.create_tenant(rand_name('tenant-new'))
+ tenant_name = data_utils.rand_name(name='tenant-new')
+ resp, tenant = self.client.create_tenant(tenant_name)
+ self.assertEqual(200, resp.status)
self.data.tenants.append(tenant)
tenants.append(tenant)
tenant_ids = map(lambda x: x['id'], tenants)
@@ -62,37 +49,10 @@
self.assertFalse(any(found), 'Tenants failed to delete')
@attr(type='gate')
- def test_tenant_delete_by_unauthorized_user(self):
- # Non-administrator 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='gate')
- 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='gate')
- def test_delete_non_existent_tenant(self):
- # Attempt to delete a non existent tenant should fail
- self.assertRaises(exceptions.NotFound, self.client.delete_tenant,
- 'junk_tenant_123456abc')
-
- @attr(type='gate')
def test_tenant_create_with_description(self):
# Create tenant with a description
- tenant_name = rand_name('tenant-')
- tenant_desc = rand_name('desc-')
+ tenant_name = data_utils.rand_name(name='tenant-')
+ tenant_desc = data_utils.rand_name(name='desc-')
resp, body = self.client.create_tenant(tenant_name,
description=tenant_desc)
tenant = body
@@ -113,7 +73,7 @@
@attr(type='gate')
def test_tenant_create_enabled(self):
# Create a tenant that is enabled
- tenant_name = rand_name('tenant-')
+ tenant_name = data_utils.rand_name(name='tenant-')
resp, body = self.client.create_tenant(tenant_name, enabled=True)
tenant = body
self.data.tenants.append(tenant)
@@ -131,7 +91,7 @@
@attr(type='gate')
def test_tenant_create_not_enabled(self):
# Create a tenant that is not enabled
- tenant_name = rand_name('tenant-')
+ tenant_name = data_utils.rand_name(name='tenant-')
resp, body = self.client.create_tenant(tenant_name, enabled=False)
tenant = body
self.data.tenants.append(tenant)
@@ -149,61 +109,18 @@
self.data.tenants.remove(tenant)
@attr(type='gate')
- 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')
-
- self.addCleanup(self.client.delete_tenant, tenant1_id)
- self.addCleanup(self.data.tenants.remove, tenant)
- self.assertRaises(exceptions.Duplicate, self.client.create_tenant,
- tenant_name)
-
- @attr(type='gate')
- def test_create_tenant_by_unauthorized_user(self):
- # Non-administrator 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='gate')
- def test_create_tenant_request_without_token(self):
- # Create tenant request without a token should not be authorized
- tenant_name = rand_name('tenant-')
- token = self.client.get_auth()
- self.client.delete_token(token)
- self.assertRaises(exceptions.Unauthorized, self.client.create_tenant,
- tenant_name)
- self.client.clear_auth()
-
- @attr(type='gate')
- def test_create_tenant_with_empty_name(self):
- # Tenant name should not be empty
- self.assertRaises(exceptions.BadRequest, self.client.create_tenant,
- name='')
-
- @attr(type='gate')
- def test_create_tenants_name_length_over_64(self):
- # Tenant name length should not be greater than 64 characters
- tenant_name = 'a' * 65
- self.assertRaises(exceptions.BadRequest, self.client.create_tenant,
- tenant_name)
-
- @attr(type='gate')
def test_tenant_update_name(self):
# Update name attribute of a tenant
- t_name1 = rand_name('tenant-')
+ t_name1 = data_utils.rand_name(name='tenant-')
resp, body = self.client.create_tenant(t_name1)
+ self.assertEqual(200, resp.status)
tenant = body
self.data.tenants.append(tenant)
t_id = body['id']
resp1_name = body['name']
- t_name2 = rand_name('tenant2-')
+ t_name2 = data_utils.rand_name(name='tenant2-')
resp, body = self.client.update_tenant(t_id, name=t_name2)
st2 = resp['status']
resp2_name = body['name']
@@ -223,16 +140,17 @@
@attr(type='gate')
def test_tenant_update_desc(self):
# Update description attribute of a tenant
- t_name = rand_name('tenant-')
- t_desc = rand_name('desc-')
+ t_name = data_utils.rand_name(name='tenant-')
+ t_desc = data_utils.rand_name(name='desc-')
resp, body = self.client.create_tenant(t_name, description=t_desc)
+ self.assertEqual(200, resp.status)
tenant = body
self.data.tenants.append(tenant)
t_id = body['id']
resp1_desc = body['description']
- t_desc2 = rand_name('desc2-')
+ t_desc2 = data_utils.rand_name(name='desc2-')
resp, body = self.client.update_tenant(t_id, description=t_desc2)
st2 = resp['status']
resp2_desc = body['description']
@@ -252,9 +170,10 @@
@attr(type='gate')
def test_tenant_update_enable(self):
# Update the enabled attribute of a tenant
- t_name = rand_name('tenant-')
+ t_name = data_utils.rand_name(name='tenant-')
t_en = False
resp, body = self.client.create_tenant(t_name, enabled=t_en)
+ self.assertEqual(200, resp.status)
tenant = body
self.data.tenants.append(tenant)
diff --git a/tempest/api/identity/admin/test_tokens.py b/tempest/api/identity/admin/test_tokens.py
new file mode 100644
index 0000000..334a5aa
--- /dev/null
+++ b/tempest/api/identity/admin/test_tokens.py
@@ -0,0 +1,58 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 Huawei Technologies Co.,LTD.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import json
+
+from tempest.api.identity import base
+from tempest.common.utils import data_utils
+from tempest.test import attr
+
+
+class TokensTestJSON(base.BaseIdentityAdminTest):
+ _interface = 'json'
+
+ @attr(type='gate')
+ def test_create_delete_token(self):
+ # get a token by username and password
+ user_name = data_utils.rand_name(name='user-')
+ user_password = data_utils.rand_name(name='pass-')
+ # first:create a tenant
+ tenant_name = data_utils.rand_name(name='tenant-')
+ resp, tenant = self.client.create_tenant(tenant_name)
+ self.assertEqual(200, resp.status)
+ self.data.tenants.append(tenant)
+ # second:create a user
+ resp, user = self.client.create_user(user_name, user_password,
+ tenant['id'], '')
+ self.assertEqual(200, resp.status)
+ self.data.users.append(user)
+ # then get a token for the user
+ rsp, body = self.token_client.auth(user_name,
+ user_password,
+ tenant['name'])
+ access_data = json.loads(body)['access']
+ self.assertEqual(rsp['status'], '200')
+ self.assertEqual(access_data['token']['tenant']['name'],
+ tenant['name'])
+ # then delete the token
+ token_id = access_data['token']['id']
+ resp, body = self.client.delete_token(token_id)
+ self.assertEqual(resp['status'], '204')
+
+
+class TokensTestXML(TokensTestJSON):
+ _interface = 'xml'
diff --git a/tempest/api/identity/admin/test_users.py b/tempest/api/identity/admin/test_users.py
index 689ab29..5d5a814 100644
--- a/tempest/api/identity/admin/test_users.py
+++ b/tempest/api/identity/admin/test_users.py
@@ -18,8 +18,7 @@
from testtools.matchers import Contains
from tempest.api.identity import base
-from tempest.common.utils.data_utils import rand_name
-from tempest import exceptions
+from tempest.common.utils import data_utils
from tempest.test import attr
@@ -29,11 +28,9 @@
@classmethod
def setUpClass(cls):
super(UsersTestJSON, cls).setUpClass()
- cls.alt_user = rand_name('test_user_')
- cls.alt_password = rand_name('pass_')
+ cls.alt_user = data_utils.rand_name('test_user_')
+ cls.alt_password = data_utils.rand_name('pass_')
cls.alt_email = cls.alt_user + '@testmail.tm'
- cls.alt_tenant = rand_name('test_tenant_')
- cls.alt_description = rand_name('desc_')
@attr(type='smoke')
def test_create_user(self):
@@ -46,65 +43,24 @@
self.assertEqual('200', resp['status'])
self.assertEqual(self.alt_user, user['name'])
- @attr(type=['negative', 'gate'])
- def test_create_user_by_unauthorized_user(self):
- # Non-administrator should not be authorized to create a user
+ @attr(type='smoke')
+ def test_create_user_with_enabled(self):
+ # Create a user with enabled : False
self.data.setup_test_tenant()
- self.assertRaises(exceptions.Unauthorized,
- self.non_admin_client.create_user, self.alt_user,
- self.alt_password, self.data.tenant['id'],
- self.alt_email)
-
- @attr(type=['negative', 'gate'])
- def test_create_user_with_empty_name(self):
- # User with an empty name should not be created
- self.data.setup_test_tenant()
- self.assertRaises(exceptions.BadRequest, self.client.create_user, '',
- self.alt_password, self.data.tenant['id'],
- self.alt_email)
-
- @attr(type=['negative', 'gate'])
- def test_create_user_with_name_length_over_255(self):
- # Length of user name filed should be restricted to 255 characters
- self.data.setup_test_tenant()
- self.assertRaises(exceptions.BadRequest, self.client.create_user,
- 'a' * 256, self.alt_password,
- self.data.tenant['id'], self.alt_email)
-
- @attr(type=['negative', 'gate'])
- def test_create_user_with_duplicate_name(self):
- # Duplicate user should not be created
- self.data.setup_test_user()
- self.assertRaises(exceptions.Duplicate, self.client.create_user,
- self.data.test_user, self.data.test_password,
- self.data.tenant['id'], self.data.test_email)
-
- @attr(type=['negative', 'gate'])
- def test_create_user_for_non_existant_tenant(self):
- # Attempt to create a user in a non-existent tenant should fail
- self.assertRaises(exceptions.NotFound, self.client.create_user,
- self.alt_user, self.alt_password, '49ffgg99999',
- self.alt_email)
-
- @attr(type=['negative', 'gate'])
- def test_create_user_request_without_a_token(self):
- # Request to create a user without a valid token should fail
- self.data.setup_test_tenant()
- # Get the token of the current client
- token = self.client.get_auth()
- # Delete the token from database
- self.client.delete_token(token)
- self.assertRaises(exceptions.Unauthorized, self.client.create_user,
- self.alt_user, self.alt_password,
- self.data.tenant['id'], self.alt_email)
-
- # Unset the token to allow further tests to generate a new token
- self.client.clear_auth()
+ name = data_utils.rand_name('test_user_')
+ resp, user = self.client.create_user(name, self.alt_password,
+ self.data.tenant['id'],
+ self.alt_email, enabled=False)
+ self.data.users.append(user)
+ self.assertEqual('200', resp['status'])
+ self.assertEqual(name, user['name'])
+ self.assertEqual('false', str(user['enabled']).lower())
+ self.assertEqual(self.alt_email, user['email'])
@attr(type='smoke')
def test_update_user(self):
# Test case to check if updating of user attributes is successful.
- test_user = rand_name('test_user_')
+ test_user = data_utils.rand_name('test_user_')
self.data.setup_test_tenant()
resp, user = self.client.create_user(test_user, self.alt_password,
self.data.tenant['id'],
@@ -112,7 +68,7 @@
# Delete the User at the end of this method
self.addCleanup(self.client.delete_user, user['id'])
# Updating user details with new values
- u_name2 = rand_name('user2-')
+ u_name2 = data_utils.rand_name('user2-')
u_email2 = u_name2 + '@testmail.tm'
resp, update_user = self.client.update_user(user['id'], name=u_name2,
email=u_email2,
@@ -132,7 +88,7 @@
@attr(type='smoke')
def test_delete_user(self):
# Delete a user
- test_user = rand_name('test_user_')
+ test_user = data_utils.rand_name('test_user_')
self.data.setup_test_tenant()
resp, user = self.client.create_user(test_user, self.alt_password,
self.data.tenant['id'],
@@ -141,20 +97,6 @@
resp, body = self.client.delete_user(user['id'])
self.assertEqual('204', resp['status'])
- @attr(type=['negative', 'gate'])
- def test_delete_users_by_unauthorized_user(self):
- # Non-administrator user should not be authorized to delete a user
- self.data.setup_test_user()
- self.assertRaises(exceptions.Unauthorized,
- self.non_admin_client.delete_user,
- self.data.user['id'])
-
- @attr(type=['negative', 'gate'])
- def test_delete_non_existant_user(self):
- # Attempt to delete a non-existent user should fail
- self.assertRaises(exceptions.NotFound, self.client.delete_user,
- 'junk12345123')
-
@attr(type='smoke')
def test_user_authentication(self):
# Valid user's token is authenticated
@@ -168,51 +110,6 @@
self.data.test_tenant)
self.assertEqual('200', resp['status'])
- @attr(type=['negative', 'gate'])
- def test_authentication_for_disabled_user(self):
- # Disabled user's token should not get authenticated
- self.data.setup_test_user()
- self.disable_user(self.data.test_user)
- self.assertRaises(exceptions.Unauthorized, self.token_client.auth,
- self.data.test_user,
- self.data.test_password,
- self.data.test_tenant)
-
- @attr(type=['negative', 'gate'])
- def test_authentication_when_tenant_is_disabled(self):
- # User's token for a disabled tenant should not be authenticated
- self.data.setup_test_user()
- self.disable_tenant(self.data.test_tenant)
- self.assertRaises(exceptions.Unauthorized, self.token_client.auth,
- self.data.test_user,
- self.data.test_password,
- self.data.test_tenant)
-
- @attr(type=['negative', 'gate'])
- def test_authentication_with_invalid_tenant(self):
- # User's token for an invalid tenant should not be authenticated
- self.data.setup_test_user()
- self.assertRaises(exceptions.Unauthorized, self.token_client.auth,
- self.data.test_user,
- self.data.test_password,
- 'junktenant1234')
-
- @attr(type=['negative', 'gate'])
- def test_authentication_with_invalid_username(self):
- # Non-existent user's token should not get authenticated
- self.data.setup_test_user()
- self.assertRaises(exceptions.Unauthorized, self.token_client.auth,
- 'junkuser123', self.data.test_password,
- self.data.test_tenant)
-
- @attr(type=['negative', 'gate'])
- def test_authentication_with_invalid_password(self):
- # User's token with invalid password should not be authenticated
- self.data.setup_test_user()
- self.assertRaises(exceptions.Unauthorized, self.token_client.auth,
- self.data.test_user, 'junkpass1234',
- self.data.test_tenant)
-
@attr(type='gate')
def test_authentication_request_without_token(self):
# Request for token authentication with a valid token in header
@@ -239,28 +136,13 @@
Contains(self.data.test_user),
"Could not find %s" % self.data.test_user)
- @attr(type=['negative', 'gate'])
- def test_get_users_by_unauthorized_user(self):
- # Non-administrator user should not be authorized to get user list
- self.data.setup_test_user()
- self.assertRaises(exceptions.Unauthorized,
- self.non_admin_client.get_users)
-
- @attr(type=['negative', 'gate'])
- def test_get_users_request_without_token(self):
- # Request to get list of users without a valid token should fail
- token = self.client.get_auth()
- self.client.delete_token(token)
- self.assertRaises(exceptions.Unauthorized, self.client.get_users)
- self.client.clear_auth()
-
@attr(type='gate')
def test_list_users_for_tenant(self):
# Return a list of all users for a tenant
self.data.setup_test_tenant()
user_ids = list()
fetched_user_ids = list()
- alt_tenant_user1 = rand_name('tenant_user1_')
+ alt_tenant_user1 = data_utils.rand_name('tenant_user1_')
resp, user1 = self.client.create_user(alt_tenant_user1, 'password1',
self.data.tenant['id'],
'user1@123')
@@ -268,7 +150,7 @@
user_ids.append(user1['id'])
self.data.users.append(user1)
- alt_tenant_user2 = rand_name('tenant_user2_')
+ alt_tenant_user2 = data_utils.rand_name('tenant_user2_')
resp, user2 = self.client.create_user(alt_tenant_user2, 'password2',
self.data.tenant['id'],
'user2@123')
@@ -303,7 +185,7 @@
role['id'])
self.assertEqual('200', resp['status'])
- alt_user2 = rand_name('second_user_')
+ alt_user2 = data_utils.rand_name('second_user_')
resp, second_user = self.client.create_user(alt_user2, 'password1',
self.data.tenant['id'],
'user2@123')
@@ -326,21 +208,6 @@
"Failed to find user %s in fetched list" %
', '.join(m_user for m_user in missing_users))
- @attr(type=['negative', 'gate'])
- def test_list_users_with_invalid_tenant(self):
- # Should not be able to return a list of all
- # users for a non-existent tenant
- # Assign invalid tenant ids
- invalid_id = list()
- invalid_id.append(rand_name('999'))
- invalid_id.append('alpha')
- invalid_id.append(rand_name("dddd@#%%^$"))
- invalid_id.append('!@#()$%^&*?<>{}[]')
- # List the users with invalid tenant id
- for invalid in invalid_id:
- self.assertRaises(exceptions.NotFound,
- self.client.list_users_for_tenant, invalid)
-
class UsersTestXML(UsersTestJSON):
_interface = 'xml'
diff --git a/tempest/api/identity/admin/test_users_negative.py b/tempest/api/identity/admin/test_users_negative.py
new file mode 100644
index 0000000..3163c16
--- /dev/null
+++ b/tempest/api/identity/admin/test_users_negative.py
@@ -0,0 +1,234 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 OpenStack Foundation
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.api.identity import base
+from tempest.common.utils import data_utils
+from tempest import exceptions
+from tempest.test import attr
+import uuid
+
+
+class UsersNegativeTestJSON(base.BaseIdentityAdminTest):
+ _interface = 'json'
+
+ @classmethod
+ def setUpClass(cls):
+ super(UsersNegativeTestJSON, cls).setUpClass()
+ cls.alt_user = data_utils.rand_name('test_user_')
+ cls.alt_password = data_utils.rand_name('pass_')
+ cls.alt_email = cls.alt_user + '@testmail.tm'
+
+ @attr(type=['negative', 'gate'])
+ def test_create_user_by_unauthorized_user(self):
+ # Non-administrator should not be authorized to create a user
+ self.data.setup_test_tenant()
+ self.assertRaises(exceptions.Unauthorized,
+ self.non_admin_client.create_user, self.alt_user,
+ self.alt_password, self.data.tenant['id'],
+ self.alt_email)
+
+ @attr(type=['negative', 'gate'])
+ def test_create_user_with_empty_name(self):
+ # User with an empty name should not be created
+ self.data.setup_test_tenant()
+ self.assertRaises(exceptions.BadRequest, self.client.create_user, '',
+ self.alt_password, self.data.tenant['id'],
+ self.alt_email)
+
+ @attr(type=['negative', 'gate'])
+ def test_create_user_with_name_length_over_255(self):
+ # Length of user name filed should be restricted to 255 characters
+ self.data.setup_test_tenant()
+ self.assertRaises(exceptions.BadRequest, self.client.create_user,
+ 'a' * 256, self.alt_password,
+ self.data.tenant['id'], self.alt_email)
+
+ @attr(type=['negative', 'gate'])
+ def test_create_user_with_duplicate_name(self):
+ # Duplicate user should not be created
+ self.data.setup_test_user()
+ self.assertRaises(exceptions.Conflict, self.client.create_user,
+ self.data.test_user, self.data.test_password,
+ self.data.tenant['id'], self.data.test_email)
+
+ @attr(type=['negative', 'gate'])
+ def test_create_user_for_non_existant_tenant(self):
+ # Attempt to create a user in a non-existent tenant should fail
+ self.assertRaises(exceptions.NotFound, self.client.create_user,
+ self.alt_user, self.alt_password, '49ffgg99999',
+ self.alt_email)
+
+ @attr(type=['negative', 'gate'])
+ def test_create_user_request_without_a_token(self):
+ # Request to create a user without a valid token should fail
+ self.data.setup_test_tenant()
+ # Get the token of the current client
+ token = self.client.get_auth()
+ # Delete the token from database
+ self.client.delete_token(token)
+ self.assertRaises(exceptions.Unauthorized, self.client.create_user,
+ self.alt_user, self.alt_password,
+ self.data.tenant['id'], self.alt_email)
+
+ # Unset the token to allow further tests to generate a new token
+ self.client.clear_auth()
+
+ @attr(type=['negative', 'gate'])
+ def test_create_user_with_enabled_non_bool(self):
+ # Attempt to create a user with valid enabled para should fail
+ self.data.setup_test_tenant()
+ name = data_utils.rand_name('test_user_')
+ self.assertRaises(exceptions.BadRequest, self.client.create_user,
+ name, self.alt_password,
+ self.data.tenant['id'],
+ self.alt_email, enabled=3)
+
+ @attr(type=['negative', 'gate'])
+ def test_update_user_for_non_existant_user(self):
+ # Attempt to update a user non-existent user should fail
+ user_name = data_utils.rand_name('user-')
+ non_existent_id = str(uuid.uuid4())
+ self.assertRaises(exceptions.NotFound, self.client.update_user,
+ non_existent_id, name=user_name)
+
+ @attr(type=['negative', 'gate'])
+ def test_update_user_request_without_a_token(self):
+ # Request to update a user without a valid token should fail
+
+ # Get the token of the current client
+ token = self.client.get_auth()
+ # Delete the token from database
+ self.client.delete_token(token)
+ self.assertRaises(exceptions.Unauthorized, self.client.update_user,
+ self.alt_user)
+
+ # Unset the token to allow further tests to generate a new token
+ self.client.clear_auth()
+
+ @attr(type=['negative', 'gate'])
+ def test_update_user_by_unauthorized_user(self):
+ # Non-administrator should not be authorized to update user
+ self.data.setup_test_tenant()
+ self.assertRaises(exceptions.Unauthorized,
+ self.non_admin_client.update_user, self.alt_user)
+
+ @attr(type=['negative', 'gate'])
+ def test_delete_users_by_unauthorized_user(self):
+ # Non-administrator user should not be authorized to delete a user
+ self.data.setup_test_user()
+ self.assertRaises(exceptions.Unauthorized,
+ self.non_admin_client.delete_user,
+ self.data.user['id'])
+
+ @attr(type=['negative', 'gate'])
+ def test_delete_non_existant_user(self):
+ # Attempt to delete a non-existent user should fail
+ self.assertRaises(exceptions.NotFound, self.client.delete_user,
+ 'junk12345123')
+
+ @attr(type=['negative', 'gate'])
+ def test_delete_user_request_without_a_token(self):
+ # Request to delete a user without a valid token should fail
+
+ # Get the token of the current client
+ token = self.client.get_auth()
+ # Delete the token from database
+ self.client.delete_token(token)
+ self.assertRaises(exceptions.Unauthorized, self.client.delete_user,
+ self.alt_user)
+
+ # Unset the token to allow further tests to generate a new token
+ self.client.clear_auth()
+
+ @attr(type=['negative', 'gate'])
+ def test_authentication_for_disabled_user(self):
+ # Disabled user's token should not get authenticated
+ self.data.setup_test_user()
+ self.disable_user(self.data.test_user)
+ self.assertRaises(exceptions.Unauthorized, self.token_client.auth,
+ self.data.test_user,
+ self.data.test_password,
+ self.data.test_tenant)
+
+ @attr(type=['negative', 'gate'])
+ def test_authentication_when_tenant_is_disabled(self):
+ # User's token for a disabled tenant should not be authenticated
+ self.data.setup_test_user()
+ self.disable_tenant(self.data.test_tenant)
+ self.assertRaises(exceptions.Unauthorized, self.token_client.auth,
+ self.data.test_user,
+ self.data.test_password,
+ self.data.test_tenant)
+
+ @attr(type=['negative', 'gate'])
+ def test_authentication_with_invalid_tenant(self):
+ # User's token for an invalid tenant should not be authenticated
+ self.data.setup_test_user()
+ self.assertRaises(exceptions.Unauthorized, self.token_client.auth,
+ self.data.test_user,
+ self.data.test_password,
+ 'junktenant1234')
+
+ @attr(type=['negative', 'gate'])
+ def test_authentication_with_invalid_username(self):
+ # Non-existent user's token should not get authenticated
+ self.data.setup_test_user()
+ self.assertRaises(exceptions.Unauthorized, self.token_client.auth,
+ 'junkuser123', self.data.test_password,
+ self.data.test_tenant)
+
+ @attr(type=['negative', 'gate'])
+ def test_authentication_with_invalid_password(self):
+ # User's token with invalid password should not be authenticated
+ self.data.setup_test_user()
+ self.assertRaises(exceptions.Unauthorized, self.token_client.auth,
+ self.data.test_user, 'junkpass1234',
+ self.data.test_tenant)
+
+ @attr(type=['negative', 'gate'])
+ def test_get_users_by_unauthorized_user(self):
+ # Non-administrator user should not be authorized to get user list
+ self.data.setup_test_user()
+ self.assertRaises(exceptions.Unauthorized,
+ self.non_admin_client.get_users)
+
+ @attr(type=['negative', 'gate'])
+ def test_get_users_request_without_token(self):
+ # Request to get list of users without a valid token should fail
+ token = self.client.get_auth()
+ self.client.delete_token(token)
+ self.assertRaises(exceptions.Unauthorized, self.client.get_users)
+ self.client.clear_auth()
+
+ @attr(type=['negative', 'gate'])
+ def test_list_users_with_invalid_tenant(self):
+ # Should not be able to return a list of all
+ # users for a non-existent tenant
+ # Assign invalid tenant ids
+ invalid_id = list()
+ invalid_id.append(data_utils.rand_name('999'))
+ invalid_id.append('alpha')
+ invalid_id.append(data_utils.rand_name("dddd@#%%^$"))
+ invalid_id.append('!@#()$%^&*?<>{}[]')
+ # List the users with invalid tenant id
+ for invalid in invalid_id:
+ self.assertRaises(exceptions.NotFound,
+ self.client.list_users_for_tenant, invalid)
+
+
+class UsersNegativeTestXML(UsersNegativeTestJSON):
+ _interface = 'xml'
diff --git a/tempest/api/identity/admin/v3/test_credentials.py b/tempest/api/identity/admin/v3/test_credentials.py
index cda5863..0b494f3 100644
--- a/tempest/api/identity/admin/v3/test_credentials.py
+++ b/tempest/api/identity/admin/v3/test_credentials.py
@@ -16,7 +16,7 @@
# under the License.
from tempest.api.identity import base
-from tempest.common.utils.data_utils import rand_name
+from tempest.common.utils import data_utils
from tempest.test import attr
@@ -29,13 +29,14 @@
cls.projects = list()
cls.creds_list = [['project_id', 'user_id', 'id'],
['access', 'secret']]
- u_name = rand_name('user-')
+ u_name = data_utils.rand_name('user-')
u_desc = '%s description' % u_name
u_email = '%s@testmail.tm' % u_name
- u_password = rand_name('pass-')
+ u_password = data_utils.rand_name('pass-')
for i in range(2):
resp, cls.project = cls.v3_client.create_project(
- rand_name('project-'), description=rand_name('project-desc-'))
+ data_utils.rand_name('project-'),
+ description=data_utils.rand_name('project-desc-'))
assert resp['status'] == '201', "Expected %s" % resp['status']
cls.projects.append(cls.project['id'])
@@ -59,7 +60,8 @@
@attr(type='smoke')
def test_credentials_create_get_update_delete(self):
- keys = [rand_name('Access-'), rand_name('Secret-')]
+ keys = [data_utils.rand_name('Access-'),
+ data_utils.rand_name('Secret-')]
resp, cred = self.creds_client.create_credential(
keys[0], keys[1], self.user_body['id'],
self.projects[0])
@@ -70,7 +72,8 @@
for value2 in self.creds_list[1]:
self.assertIn(value2, cred['blob'])
- new_keys = [rand_name('NewAccess-'), rand_name('NewSecret-')]
+ new_keys = [data_utils.rand_name('NewAccess-'),
+ data_utils.rand_name('NewSecret-')]
resp, update_body = self.creds_client.update_credential(
cred['id'], access_key=new_keys[0], secret_key=new_keys[1],
project_id=self.projects[1])
@@ -97,7 +100,8 @@
for i in range(2):
resp, cred = self.creds_client.create_credential(
- rand_name('Access-'), rand_name('Secret-'),
+ data_utils.rand_name('Access-'),
+ data_utils.rand_name('Secret-'),
self.user_body['id'], self.projects[0])
self.assertEqual(resp['status'], '201')
created_cred_ids.append(cred['id'])
diff --git a/tempest/api/identity/admin/v3/test_domains.py b/tempest/api/identity/admin/v3/test_domains.py
index 2fbef77..ed776cd 100644
--- a/tempest/api/identity/admin/v3/test_domains.py
+++ b/tempest/api/identity/admin/v3/test_domains.py
@@ -17,7 +17,7 @@
from tempest.api.identity import base
-from tempest.common.utils.data_utils import rand_name
+from tempest.common.utils import data_utils
from tempest.test import attr
@@ -38,7 +38,8 @@
fetched_ids = list()
for _ in range(3):
_, domain = self.v3_client.create_domain(
- rand_name('domain-'), description=rand_name('domain-desc-'))
+ data_utils.rand_name('domain-'),
+ description=data_utils.rand_name('domain-desc-'))
# Delete the domain at the end of this method
self.addCleanup(self._delete_domain, domain['id'])
domain_ids.append(domain['id'])
@@ -52,8 +53,8 @@
@attr(type='smoke')
def test_create_update_delete_domain(self):
- d_name = rand_name('domain-')
- d_desc = rand_name('domain-desc-')
+ d_name = data_utils.rand_name('domain-')
+ d_desc = data_utils.rand_name('domain-desc-')
resp_1, domain = self.v3_client.create_domain(
d_name, description=d_desc)
self.assertEqual(resp_1['status'], '201')
@@ -70,8 +71,8 @@
self.assertEqual(True, domain['enabled'])
else:
self.assertEqual('true', str(domain['enabled']).lower())
- new_desc = rand_name('new-desc-')
- new_name = rand_name('new-name-')
+ new_desc = data_utils.rand_name('new-desc-')
+ new_name = data_utils.rand_name('new-name-')
resp_2, updated_domain = self.v3_client.update_domain(
domain['id'], name=new_name, description=new_desc)
diff --git a/tempest/api/identity/admin/v3/test_endpoints.py b/tempest/api/identity/admin/v3/test_endpoints.py
index 02a6f5b..d4d2109 100644
--- a/tempest/api/identity/admin/v3/test_endpoints.py
+++ b/tempest/api/identity/admin/v3/test_endpoints.py
@@ -16,7 +16,7 @@
# under the License.
from tempest.api.identity import base
-from tempest.common.utils.data_utils import rand_name
+from tempest.common.utils import data_utils
from tempest.test import attr
@@ -29,9 +29,9 @@
cls.identity_client = cls.client
cls.client = cls.endpoints_client
cls.service_ids = list()
- s_name = rand_name('service-')
- s_type = rand_name('type--')
- s_description = rand_name('description-')
+ s_name = data_utils.rand_name('service-')
+ s_type = data_utils.rand_name('type--')
+ s_description = data_utils.rand_name('description-')
resp, cls.service_data =\
cls.identity_client.create_service(s_name, s_type,
description=s_description)
@@ -40,8 +40,8 @@
# Create endpoints so as to use for LIST and GET test cases
cls.setup_endpoints = list()
for i in range(2):
- region = rand_name('region')
- url = rand_name('url')
+ region = data_utils.rand_name('region')
+ url = data_utils.rand_name('url')
interface = 'public'
resp, endpoint = cls.client.create_endpoint(
cls.service_id, interface, url, region=region, enabled=True)
@@ -69,8 +69,8 @@
@attr(type='gate')
def test_create_list_delete_endpoint(self):
- region = rand_name('region')
- url = rand_name('url')
+ region = data_utils.rand_name('region')
+ url = data_utils.rand_name('url')
interface = 'public'
resp, endpoint =\
self.client.create_endpoint(self.service_id, interface, url,
@@ -97,24 +97,24 @@
def test_update_endpoint(self):
# Creating an endpoint so as to check update endpoint
# with new values
- region1 = rand_name('region')
- url1 = rand_name('url')
+ region1 = data_utils.rand_name('region')
+ url1 = data_utils.rand_name('url')
interface1 = 'public'
resp, endpoint_for_update =\
self.client.create_endpoint(self.service_id, interface1,
url1, region=region1,
enabled=True)
# Creating service so as update endpoint with new service ID
- s_name = rand_name('service-')
- s_type = rand_name('type--')
- s_description = rand_name('description-')
+ s_name = data_utils.rand_name('service-')
+ s_type = data_utils.rand_name('type--')
+ s_description = data_utils.rand_name('description-')
resp, self.service2 =\
self.identity_client.create_service(s_name, s_type,
description=s_description)
self.service_ids.append(self.service2['id'])
# Updating endpoint with new values
- region2 = rand_name('region')
- url2 = rand_name('url')
+ region2 = data_utils.rand_name('region')
+ url2 = data_utils.rand_name('url')
interface2 = 'internal'
resp, endpoint = \
self.client.update_endpoint(endpoint_for_update['id'],
diff --git a/tempest/api/identity/admin/v3/test_policies.py b/tempest/api/identity/admin/v3/test_policies.py
index 737a0e0..48f8fcd 100644
--- a/tempest/api/identity/admin/v3/test_policies.py
+++ b/tempest/api/identity/admin/v3/test_policies.py
@@ -16,7 +16,7 @@
# under the License.
from tempest.api.identity import base
-from tempest.common.utils.data_utils import rand_name
+from tempest.common.utils import data_utils
from tempest.test import attr
@@ -33,8 +33,8 @@
policy_ids = list()
fetched_ids = list()
for _ in range(3):
- blob = rand_name('BlobName-')
- policy_type = rand_name('PolicyType-')
+ blob = data_utils.rand_name('BlobName-')
+ policy_type = data_utils.rand_name('PolicyType-')
resp, policy = self.policy_client.create_policy(blob,
policy_type)
# Delete the Policy at the end of this method
@@ -51,8 +51,8 @@
@attr(type='smoke')
def test_create_update_delete_policy(self):
# Test to update policy
- blob = rand_name('BlobName-')
- policy_type = rand_name('PolicyType-')
+ blob = data_utils.rand_name('BlobName-')
+ policy_type = data_utils.rand_name('PolicyType-')
resp, policy = self.policy_client.create_policy(blob, policy_type)
self.addCleanup(self._delete_policy, policy['id'])
self.assertIn('id', policy)
@@ -64,7 +64,7 @@
resp, fetched_policy = self.policy_client.get_policy(policy['id'])
self.assertEqual(resp['status'], '200')
# Update policy
- update_type = rand_name('UpdatedPolicyType-')
+ update_type = data_utils.rand_name('UpdatedPolicyType-')
resp, data = self.policy_client.update_policy(
policy['id'], type=update_type)
self.assertIn('type', data)
diff --git a/tempest/api/identity/admin/v3/test_projects.py b/tempest/api/identity/admin/v3/test_projects.py
index 36ced70..cbfcd04 100644
--- a/tempest/api/identity/admin/v3/test_projects.py
+++ b/tempest/api/identity/admin/v3/test_projects.py
@@ -16,7 +16,7 @@
# under the License.
from tempest.api.identity import base
-from tempest.common.utils.data_utils import rand_name
+from tempest.common.utils import data_utils
from tempest import exceptions
from tempest.test import attr
@@ -35,7 +35,7 @@
# Create several projects and delete them
for _ in xrange(3):
resp, project = self.v3_client.create_project(
- rand_name('project-new'))
+ data_utils.rand_name('project-new'))
self.addCleanup(self._delete_project, project['id'])
resp, list_projects = self.v3_client.list_projects()
@@ -47,8 +47,8 @@
@attr(type='gate')
def test_project_create_with_description(self):
# Create project with a description
- project_name = rand_name('project-')
- project_desc = rand_name('desc-')
+ project_name = data_utils.rand_name('project-')
+ project_desc = data_utils.rand_name('desc-')
resp, project = self.v3_client.create_project(
project_name, description=project_desc)
self.v3data.projects.append(project)
@@ -66,7 +66,7 @@
@attr(type='gate')
def test_project_create_enabled(self):
# Create a project that is enabled
- project_name = rand_name('project-')
+ project_name = data_utils.rand_name('project-')
resp, project = self.v3_client.create_project(
project_name, enabled=True)
self.v3data.projects.append(project)
@@ -82,7 +82,7 @@
@attr(type='gate')
def test_project_create_not_enabled(self):
# Create a project that is not enabled
- project_name = rand_name('project-')
+ project_name = data_utils.rand_name('project-')
resp, project = self.v3_client.create_project(
project_name, enabled=False)
self.v3data.projects.append(project)
@@ -99,13 +99,13 @@
@attr(type='gate')
def test_project_update_name(self):
# Update name attribute of a project
- p_name1 = rand_name('project-')
+ p_name1 = data_utils.rand_name('project-')
resp, project = self.v3_client.create_project(p_name1)
self.v3data.projects.append(project)
resp1_name = project['name']
- p_name2 = rand_name('project2-')
+ p_name2 = data_utils.rand_name('project2-')
resp, body = self.v3_client.update_project(project['id'], name=p_name2)
st2 = resp['status']
resp2_name = body['name']
@@ -122,14 +122,14 @@
@attr(type='gate')
def test_project_update_desc(self):
# Update description attribute of a project
- p_name = rand_name('project-')
- p_desc = rand_name('desc-')
+ p_name = data_utils.rand_name('project-')
+ p_desc = data_utils.rand_name('desc-')
resp, project = self.v3_client.create_project(
p_name, description=p_desc)
self.v3data.projects.append(project)
resp1_desc = project['description']
- p_desc2 = rand_name('desc2-')
+ p_desc2 = data_utils.rand_name('desc2-')
resp, body = self.v3_client.update_project(
project['id'], description=p_desc2)
st2 = resp['status']
@@ -147,7 +147,7 @@
@attr(type='gate')
def test_project_update_enable(self):
# Update the enabled attribute of a project
- p_name = rand_name('project-')
+ p_name = data_utils.rand_name('project-')
p_en = False
resp, project = self.v3_client.create_project(p_name, enabled=p_en)
self.v3data.projects.append(project)
@@ -173,15 +173,15 @@
def test_associate_user_to_project(self):
#Associate a user to a project
#Create a Project
- p_name = rand_name('project-')
+ p_name = data_utils.rand_name('project-')
resp, project = self.v3_client.create_project(p_name)
self.v3data.projects.append(project)
#Create a User
- u_name = rand_name('user-')
+ u_name = data_utils.rand_name('user-')
u_desc = u_name + 'description'
u_email = u_name + '@testmail.tm'
- u_password = rand_name('pass-')
+ u_password = data_utils.rand_name('pass-')
resp, user = self.v3_client.create_user(
u_name, description=u_desc, password=u_password,
email=u_email, project_id=project['id'])
@@ -207,17 +207,17 @@
@attr(type=['negative', 'gate'])
def test_project_create_duplicate(self):
# Project names should be unique
- project_name = rand_name('project-dup-')
+ project_name = data_utils.rand_name('project-dup-')
resp, project = self.v3_client.create_project(project_name)
self.v3data.projects.append(project)
self.assertRaises(
- exceptions.Duplicate, self.v3_client.create_project, project_name)
+ exceptions.Conflict, self.v3_client.create_project, project_name)
@attr(type=['negative', 'gate'])
def test_create_project_by_unauthorized_user(self):
# Non-admin user should not be authorized to create a project
- project_name = rand_name('project-')
+ project_name = data_utils.rand_name('project-')
self.assertRaises(
exceptions.Unauthorized, self.v3_non_admin_client.create_project,
project_name)
@@ -238,7 +238,7 @@
@attr(type=['negative', 'gate'])
def test_project_delete_by_unauthorized_user(self):
# Non-admin user should not be able to delete a project
- project_name = rand_name('project-')
+ project_name = data_utils.rand_name('project-')
resp, project = self.v3_client.create_project(project_name)
self.v3data.projects.append(project)
self.assertRaises(
diff --git a/tempest/api/identity/admin/v3/test_roles.py b/tempest/api/identity/admin/v3/test_roles.py
index a238c46..a6a2bd7 100644
--- a/tempest/api/identity/admin/v3/test_roles.py
+++ b/tempest/api/identity/admin/v3/test_roles.py
@@ -16,7 +16,7 @@
# under the License.
from tempest.api.identity import base
-from tempest.common.utils.data_utils import rand_name
+from tempest.common.utils import data_utils
from tempest.test import attr
@@ -27,23 +27,26 @@
def setUpClass(cls):
super(RolesV3TestJSON, cls).setUpClass()
cls.fetched_role_ids = list()
- u_name = rand_name('user-')
+ u_name = data_utils.rand_name('user-')
u_desc = '%s description' % u_name
u_email = '%s@testmail.tm' % u_name
- u_password = rand_name('pass-')
+ u_password = data_utils.rand_name('pass-')
resp = [None] * 5
resp[0], cls.project = cls.v3_client.create_project(
- rand_name('project-'), description=rand_name('project-desc-'))
+ data_utils.rand_name('project-'),
+ description=data_utils.rand_name('project-desc-'))
resp[1], cls.domain = cls.v3_client.create_domain(
- rand_name('domain-'), description=rand_name('domain-desc-'))
+ data_utils.rand_name('domain-'),
+ description=data_utils.rand_name('domain-desc-'))
resp[2], cls.group_body = cls.v3_client.create_group(
- rand_name('Group-'), project_id=cls.project['id'],
+ data_utils.rand_name('Group-'), project_id=cls.project['id'],
domain_id=cls.domain['id'])
resp[3], cls.user_body = cls.v3_client.create_user(
u_name, description=u_desc, password=u_password,
email=u_email, project_id=cls.project['id'],
domain_id=cls.domain['id'])
- resp[4], cls.role = cls.v3_client.create_role(rand_name('Role-'))
+ resp[4], cls.role = cls.v3_client.create_role(
+ data_utils.rand_name('Role-'))
for r in resp:
assert r['status'] == '201', "Expected: %s" % r['status']
@@ -69,14 +72,14 @@
@attr(type='smoke')
def test_role_create_update_get(self):
- r_name = rand_name('Role-')
+ r_name = data_utils.rand_name('Role-')
resp, role = self.v3_client.create_role(r_name)
self.addCleanup(self.v3_client.delete_role, role['id'])
self.assertEqual(resp['status'], '201')
self.assertIn('name', role)
self.assertEqual(role['name'], r_name)
- new_name = rand_name('NewRole-')
+ new_name = data_utils.rand_name('NewRole-')
resp, updated_role = self.v3_client.update_role(new_name, role['id'])
self.assertEqual(resp['status'], '200')
self.assertIn('name', updated_role)
diff --git a/tempest/api/identity/admin/v3/test_services.py b/tempest/api/identity/admin/v3/test_services.py
index bfa0d84..1751638 100644
--- a/tempest/api/identity/admin/v3/test_services.py
+++ b/tempest/api/identity/admin/v3/test_services.py
@@ -17,7 +17,7 @@
from tempest.api.identity import base
-from tempest.common.utils.data_utils import rand_name
+from tempest.common.utils import data_utils
from tempest.test import attr
@@ -27,9 +27,9 @@
@attr(type='gate')
def test_update_service(self):
# Update description attribute of service
- name = rand_name('service-')
- type = rand_name('type--')
- description = rand_name('description-')
+ name = data_utils.rand_name('service-')
+ type = data_utils.rand_name('type--')
+ description = data_utils.rand_name('description-')
resp, body = self.client.create_service(
name, type, description=description)
self.assertEqual('200', resp['status'])
@@ -39,7 +39,7 @@
s_id = body['id']
resp1_desc = body['description']
- s_desc2 = rand_name('desc2-')
+ s_desc2 = data_utils.rand_name('desc2-')
resp, body = self.service_client.update_service(
s_id, description=s_desc2)
resp2_desc = body['description']
diff --git a/tempest/api/identity/admin/v3/test_tokens.py b/tempest/api/identity/admin/v3/test_tokens.py
index f8a62a6..2541e25 100644
--- a/tempest/api/identity/admin/v3/test_tokens.py
+++ b/tempest/api/identity/admin/v3/test_tokens.py
@@ -16,7 +16,7 @@
# under the License.
from tempest.api.identity import base
-from tempest.common.utils.data_utils import rand_name
+from tempest.common.utils import data_utils
from tempest import exceptions
from tempest.test import attr
@@ -28,10 +28,10 @@
def test_tokens(self):
# Valid user's token is authenticated
# Create a User
- u_name = rand_name('user-')
+ u_name = data_utils.rand_name('user-')
u_desc = '%s-description' % u_name
u_email = '%s@testmail.tm' % u_name
- u_password = rand_name('pass-')
+ u_password = data_utils.rand_name('pass-')
resp, user = self.v3_client.create_user(
u_name, description=u_desc, password=u_password,
email=u_email)
diff --git a/tempest/api/identity/admin/v3/test_users.py b/tempest/api/identity/admin/v3/test_users.py
index 50e9702..b1c4d82 100644
--- a/tempest/api/identity/admin/v3/test_users.py
+++ b/tempest/api/identity/admin/v3/test_users.py
@@ -16,7 +16,7 @@
# under the License.
from tempest.api.identity import base
-from tempest.common.utils.data_utils import rand_name
+from tempest.common.utils import data_utils
from tempest.test import attr
@@ -27,10 +27,10 @@
def test_user_update(self):
# Test case to check if updating of user attributes is successful.
# Creating first user
- u_name = rand_name('user-')
+ u_name = data_utils.rand_name('user-')
u_desc = u_name + 'description'
u_email = u_name + '@testmail.tm'
- u_password = rand_name('pass-')
+ u_password = data_utils.rand_name('pass-')
resp, user = self.v3_client.create_user(
u_name, description=u_desc, password=u_password,
email=u_email, enabled=False)
@@ -38,11 +38,12 @@
self.addCleanup(self.v3_client.delete_user, user['id'])
# Creating second project for updation
resp, project = self.v3_client.create_project(
- rand_name('project-'), description=rand_name('project-desc-'))
+ data_utils.rand_name('project-'),
+ description=data_utils.rand_name('project-desc-'))
# Delete the Project at the end of this method
self.addCleanup(self.v3_client.delete_project, project['id'])
# Updating user details with new values
- u_name2 = rand_name('user2-')
+ u_name2 = data_utils.rand_name('user2-')
u_email2 = u_name2 + '@testmail.tm'
u_description2 = u_name2 + ' description'
resp, update_user = self.v3_client.update_user(
@@ -73,21 +74,23 @@
assigned_project_ids = list()
fetched_project_ids = list()
_, u_project = self.v3_client.create_project(
- rand_name('project-'), description=rand_name('project-desc-'))
+ data_utils.rand_name('project-'),
+ description=data_utils.rand_name('project-desc-'))
# Delete the Project at the end of this method
self.addCleanup(self.v3_client.delete_project, u_project['id'])
# Create a user.
- u_name = rand_name('user-')
+ u_name = data_utils.rand_name('user-')
u_desc = u_name + 'description'
u_email = u_name + '@testmail.tm'
- u_password = rand_name('pass-')
+ u_password = data_utils.rand_name('pass-')
_, user_body = self.v3_client.create_user(
u_name, description=u_desc, password=u_password,
email=u_email, enabled=False, project_id=u_project['id'])
# Delete the User at the end of this method
self.addCleanup(self.v3_client.delete_user, user_body['id'])
# Creating Role
- _, role_body = self.v3_client.create_role(rand_name('role-'))
+ _, role_body = self.v3_client.create_role(
+ data_utils.rand_name('role-'))
# Delete the Role at the end of this method
self.addCleanup(self.v3_client.delete_role, role_body['id'])
@@ -96,7 +99,8 @@
for i in range(2):
# Creating project so as to assign role
_, project_body = self.v3_client.create_project(
- rand_name('project-'), description=rand_name('project-desc-'))
+ data_utils.rand_name('project-'),
+ description=data_utils.rand_name('project-desc-'))
_, project = self.v3_client.get_project(project_body['id'])
# Delete the Project at the end of this method
self.addCleanup(self.v3_client.delete_project, project_body['id'])
diff --git a/tempest/api/identity/base.py b/tempest/api/identity/base.py
index ab89af4..876edfd 100644
--- a/tempest/api/identity/base.py
+++ b/tempest/api/identity/base.py
@@ -17,7 +17,7 @@
from tempest import clients
-from tempest.common.utils.data_utils import rand_name
+from tempest.common.utils import data_utils
import tempest.test
@@ -94,8 +94,8 @@
def setup_test_user(self):
"""Set up a test user."""
self.setup_test_tenant()
- self.test_user = rand_name('test_user_')
- self.test_password = rand_name('pass_')
+ self.test_user = data_utils.rand_name('test_user_')
+ self.test_password = data_utils.rand_name('pass_')
self.test_email = self.test_user + '@testmail.tm'
resp, self.user = self.client.create_user(self.test_user,
self.test_password,
@@ -105,8 +105,8 @@
def setup_test_tenant(self):
"""Set up a test tenant."""
- self.test_tenant = rand_name('test_tenant_')
- self.test_description = rand_name('desc_')
+ self.test_tenant = data_utils.rand_name('test_tenant_')
+ self.test_description = data_utils.rand_name('desc_')
resp, self.tenant = self.client.create_tenant(
name=self.test_tenant,
description=self.test_description)
@@ -114,15 +114,15 @@
def setup_test_role(self):
"""Set up a test role."""
- self.test_role = rand_name('role')
+ self.test_role = data_utils.rand_name('role')
resp, self.role = self.client.create_role(self.test_role)
self.roles.append(self.role)
def setup_test_v3_user(self):
"""Set up a test v3 user."""
self.setup_test_project()
- self.test_user = rand_name('test_user_')
- self.test_password = rand_name('pass_')
+ self.test_user = data_utils.rand_name('test_user_')
+ self.test_password = data_utils.rand_name('pass_')
self.test_email = self.test_user + '@testmail.tm'
resp, self.v3_user = self.client.create_user(self.test_user,
self.test_password,
@@ -132,8 +132,8 @@
def setup_test_project(self):
"""Set up a test project."""
- self.test_project = rand_name('test_project_')
- self.test_description = rand_name('desc_')
+ self.test_project = data_utils.rand_name('test_project_')
+ self.test_description = data_utils.rand_name('desc_')
resp, self.project = self.client.create_project(
name=self.test_project,
description=self.test_description)
@@ -141,7 +141,7 @@
def setup_test_v3_role(self):
"""Set up a test v3 role."""
- self.test_role = rand_name('role')
+ self.test_role = data_utils.rand_name('role')
resp, self.v3_role = self.client.create_role(self.test_role)
self.v3_roles.append(self.v3_role)
diff --git a/tempest/api/image/base.py b/tempest/api/image/base.py
index 4f54a15..28ed5b6 100644
--- a/tempest/api/image/base.py
+++ b/tempest/api/image/base.py
@@ -14,9 +14,11 @@
# License for the specific language governing permissions and limitations
# under the License.
+import cStringIO as StringIO
+
from tempest import clients
from tempest.common import isolated_creds
-from tempest.common.utils.data_utils import rand_name
+from tempest.common.utils import data_utils
from tempest import exceptions
from tempest.openstack.common import log as logging
import tempest.test
@@ -61,7 +63,7 @@
@classmethod
def create_image(cls, **kwargs):
"""Wrapper that returns a test image."""
- name = rand_name(cls.__name__ + "-instance")
+ name = data_utils.rand_name(cls.__name__ + "-instance")
if 'name' in kwargs:
name = kwargs.pop('name')
@@ -74,17 +76,6 @@
cls.created_images.append(image['id'])
return resp, image
- @classmethod
- def _check_version(cls, version):
- __, versions = cls.client.get_versions()
- if version == 'v2.0':
- if 'v2.0' in versions:
- return True
- elif version == 'v1.0':
- if 'v1.1' in versions or 'v1.0' in versions:
- return True
- return False
-
class BaseV1ImageTest(BaseImageTest):
@@ -92,17 +83,84 @@
def setUpClass(cls):
super(BaseV1ImageTest, cls).setUpClass()
cls.client = cls.os.image_client
- if not cls._check_version('v1.0'):
+ if not cls.config.image_feature_enabled.api_v1:
msg = "Glance API v1 not supported"
raise cls.skipException(msg)
+class BaseV1ImageMembersTest(BaseV1ImageTest):
+ @classmethod
+ def setUpClass(cls):
+ super(BaseV1ImageMembersTest, cls).setUpClass()
+ if cls.config.compute.allow_tenant_isolation:
+ creds = cls.isolated_creds.get_alt_creds()
+ username, tenant_name, password = creds
+ cls.os_alt = clients.Manager(username=username,
+ password=password,
+ tenant_name=tenant_name)
+ cls.alt_tenant_id = cls.isolated_creds.get_alt_tenant()['id']
+ else:
+ cls.os_alt = clients.AltManager()
+ identity_client = cls._get_identity_admin_client()
+ cls.alt_tenant_id = identity_client.get_tenant_by_name(
+ cls.os_alt.tenant_name)['id']
+
+ cls.alt_img_cli = cls.os_alt.image_client
+
+ def _create_image(self):
+ image_file = StringIO.StringIO('*' * 1024)
+ resp, image = self.create_image(container_format='bare',
+ disk_format='raw',
+ is_public=False,
+ data=image_file)
+ self.assertEqual(201, resp.status)
+ image_id = image['id']
+ return image_id
+
+
class BaseV2ImageTest(BaseImageTest):
@classmethod
def setUpClass(cls):
super(BaseV2ImageTest, cls).setUpClass()
cls.client = cls.os.image_client_v2
- if not cls._check_version('v2.0'):
+ if not cls.config.image_feature_enabled.api_v2:
msg = "Glance API v2 not supported"
raise cls.skipException(msg)
+
+
+class BaseV2MemeberImageTest(BaseImageTest):
+
+ @classmethod
+ def setUpClass(cls):
+ super(BaseV2MemeberImageTest, cls).setUpClass()
+ if cls.config.compute.allow_tenant_isolation:
+ creds = cls.isolated_creds.get_alt_creds()
+ username, tenant_name, password = creds
+ cls.os_alt = clients.Manager(username=username,
+ password=password,
+ tenant_name=tenant_name,
+ interface=cls._interface)
+ cls.alt_tenant_id = cls.isolated_creds.get_alt_tenant()['id']
+ else:
+ cls.os_alt = clients.AltManager()
+ alt_tenant_name = cls.os_alt.tenant_name
+ identity_client = cls._get_identity_admin_client()
+ cls.alt_tenant_id = identity_client.get_tenant_by_name(
+ alt_tenant_name)['id']
+ cls.os_img_client = cls.os.image_client_v2
+ cls.alt_img_client = cls.os_alt.image_client_v2
+
+ def _list_image_ids_as_alt(self):
+ _, image_list = self.alt_img_client.image_list()
+ image_ids = map(lambda x: x['id'], image_list)
+ return image_ids
+
+ def _create_image(self):
+ name = data_utils.rand_name('image')
+ resp, image = self.os_img_client.create_image(name,
+ container_format='bare',
+ disk_format='raw')
+ image_id = image['id']
+ self.addCleanup(self.os_img_client.delete_image, image_id)
+ return image_id
diff --git a/tempest/api/image/v1/test_image_members.py b/tempest/api/image/v1/test_image_members.py
index e9c395e..437527d 100644
--- a/tempest/api/image/v1/test_image_members.py
+++ b/tempest/api/image/v1/test_image_members.py
@@ -14,60 +14,36 @@
# License for the specific language governing permissions and limitations
# under the License.
-import cStringIO as StringIO
from tempest.api.image import base
-from tempest import clients
-from tempest.common.utils.data_utils import rand_name
-from tempest import exceptions
from tempest.test import attr
-class ImageMembersTests(base.BaseV1ImageTest):
-
- @classmethod
- def setUpClass(cls):
- super(ImageMembersTests, cls).setUpClass()
- admin = clients.AdminManager(interface='json')
- cls.admin_client = admin.identity_client
- cls.tenants = cls._get_tenants()
-
- @classmethod
- def _get_tenants(cls):
- resp, tenants = cls.admin_client.list_tenants()
- tenants = map(lambda x: x['id'], tenants)
- return tenants
-
- def _create_image(self):
- image_file = StringIO.StringIO('*' * 1024)
- resp, image = self.create_image(container_format='bare',
- disk_format='raw',
- is_public=True,
- data=image_file)
- self.assertEqual(201, resp.status)
- image_id = image['id']
- return image_id
+class ImageMembersTest(base.BaseV1ImageMembersTest):
@attr(type='gate')
def test_add_image_member(self):
image = self._create_image()
- resp = self.client.add_member(self.tenants[0], image)
+ resp = self.client.add_member(self.alt_tenant_id, image)
self.assertEqual(204, resp.status)
resp, body = self.client.get_image_membership(image)
self.assertEqual(200, resp.status)
members = body['members']
members = map(lambda x: x['member_id'], members)
- self.assertIn(self.tenants[0], members)
+ self.assertIn(self.alt_tenant_id, members)
+ # get image as alt user
+ resp, body = self.alt_img_cli.get_image(image)
+ self.assertEqual(200, resp.status)
@attr(type='gate')
def test_get_shared_images(self):
image = self._create_image()
- resp = self.client.add_member(self.tenants[0], image)
+ resp = self.client.add_member(self.alt_tenant_id, image)
self.assertEqual(204, resp.status)
share_image = self._create_image()
- resp = self.client.add_member(self.tenants[0], share_image)
+ resp = self.client.add_member(self.alt_tenant_id, share_image)
self.assertEqual(204, resp.status)
- resp, body = self.client.get_shared_images(self.tenants[0])
+ resp, body = self.client.get_shared_images(self.alt_tenant_id)
self.assertEqual(200, resp.status)
images = body['shared_images']
images = map(lambda x: x['image_id'], images)
@@ -77,33 +53,11 @@
@attr(type='gate')
def test_remove_member(self):
image_id = self._create_image()
- resp = self.client.add_member(self.tenants[0], image_id)
+ resp = self.client.add_member(self.alt_tenant_id, image_id)
self.assertEqual(204, resp.status)
- resp = self.client.delete_member(self.tenants[0], image_id)
+ resp = self.client.delete_member(self.alt_tenant_id, image_id)
self.assertEqual(204, resp.status)
resp, body = self.client.get_image_membership(image_id)
self.assertEqual(200, resp.status)
members = body['members']
- self.assertEqual(0, len(members))
-
- @attr(type=['negative', 'gate'])
- def test_add_member_with_non_existing_image(self):
- # Add member with non existing image.
- non_exist_image = rand_name('image_')
- self.assertRaises(exceptions.NotFound, self.client.add_member,
- self.tenants[0], non_exist_image)
-
- @attr(type=['negative', 'gate'])
- def test_delete_member_with_non_existing_image(self):
- # Delete member with non existing image.
- non_exist_image = rand_name('image_')
- self.assertRaises(exceptions.NotFound, self.client.delete_member,
- self.tenants[0], non_exist_image)
-
- @attr(type=['negative', 'gate'])
- def test_delete_member_with_non_existing_tenant(self):
- # Delete member with non existing tenant.
- image_id = self._create_image()
- non_exist_tenant = rand_name('tenant_')
- self.assertRaises(exceptions.NotFound, self.client.delete_member,
- non_exist_tenant, image_id)
+ self.assertEqual(0, len(members), str(members))
diff --git a/tempest/api/image/v1/test_image_members_negative.py b/tempest/api/image/v1/test_image_members_negative.py
new file mode 100644
index 0000000..83f7a0f
--- /dev/null
+++ b/tempest/api/image/v1/test_image_members_negative.py
@@ -0,0 +1,54 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 IBM Corp.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+
+from tempest.api.image import base
+from tempest.common.utils import data_utils
+from tempest import exceptions
+from tempest.test import attr
+
+
+class ImageMembersNegativeTest(base.BaseV1ImageMembersTest):
+
+ @attr(type=['negative', 'gate'])
+ def test_add_member_with_non_existing_image(self):
+ # Add member with non existing image.
+ non_exist_image = data_utils.rand_uuid()
+ self.assertRaises(exceptions.NotFound, self.client.add_member,
+ self.alt_tenant_id, non_exist_image)
+
+ @attr(type=['negative', 'gate'])
+ def test_delete_member_with_non_existing_image(self):
+ # Delete member with non existing image.
+ non_exist_image = data_utils.rand_uuid()
+ self.assertRaises(exceptions.NotFound, self.client.delete_member,
+ self.alt_tenant_id, non_exist_image)
+
+ @attr(type=['negative', 'gate'])
+ def test_delete_member_with_non_existing_tenant(self):
+ # Delete member with non existing tenant.
+ image_id = self._create_image()
+ non_exist_tenant = data_utils.rand_uuid_hex()
+ self.assertRaises(exceptions.NotFound, self.client.delete_member,
+ non_exist_tenant, image_id)
+
+ @attr(type=['negative', 'gate'])
+ def test_get_image_without_membership(self):
+ # Image is hidden from another tenants.
+ image_id = self._create_image()
+ self.assertRaises(exceptions.NotFound,
+ self.alt_img_cli.get_image,
+ image_id)
diff --git a/tempest/api/image/v1/test_images.py b/tempest/api/image/v1/test_images.py
index 90ffeae..558e2ec 100644
--- a/tempest/api/image/v1/test_images.py
+++ b/tempest/api/image/v1/test_images.py
@@ -18,7 +18,6 @@
import cStringIO as StringIO
from tempest.api.image import base
-from tempest import exceptions
from tempest.test import attr
@@ -26,17 +25,6 @@
"""Here we test the registration and creation of images."""
@attr(type='gate')
- def test_register_with_invalid_container_format(self):
- # Negative tests for invalid data supplied to POST /images
- self.assertRaises(exceptions.BadRequest, self.client.create_image,
- 'test', 'wrong', 'vhd')
-
- @attr(type='gate')
- def test_register_with_invalid_disk_format(self):
- self.assertRaises(exceptions.BadRequest, self.client.create_image,
- 'test', 'bare', 'wrong')
-
- @attr(type='gate')
def test_register_then_upload(self):
# Register, then upload an image
properties = {'prop1': 'val1'}
@@ -108,6 +96,8 @@
self.assertEqual(40, body.get('min_ram'))
for key, val in properties.items():
self.assertEqual(val, body.get('properties')[key])
+ resp, body = self.client.delete_image(body['id'])
+ self.assertEqual('200', resp['status'])
class ListImagesTest(base.BaseV1ImageTest):
diff --git a/tempest/api/image/v1/test_images_negative.py b/tempest/api/image/v1/test_images_negative.py
new file mode 100644
index 0000000..1bcf120
--- /dev/null
+++ b/tempest/api/image/v1/test_images_negative.py
@@ -0,0 +1,72 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 IBM Corp.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.api.image import base
+from tempest import exceptions
+from tempest.test import attr
+
+
+class CreateDeleteImagesNegativeTest(base.BaseV1ImageTest):
+ """Here are negative tests for the deletion and creation of images."""
+
+ @attr(type=['negative', 'gate'])
+ def test_register_with_invalid_container_format(self):
+ # Negative tests for invalid data supplied to POST /images
+ self.assertRaises(exceptions.BadRequest, self.client.create_image,
+ 'test', 'wrong', 'vhd')
+
+ @attr(type=['negative', 'gate'])
+ def test_register_with_invalid_disk_format(self):
+ self.assertRaises(exceptions.BadRequest, self.client.create_image,
+ 'test', 'bare', 'wrong')
+
+ @attr(type=['negative', 'gate'])
+ def test_delete_image_with_invalid_image_id(self):
+ # An image should not be deleted with invalid image id
+ self.assertRaises(exceptions.NotFound, self.client.delete_image,
+ '!@$%^&*()')
+
+ @attr(type=['negative', 'gate'])
+ def test_delete_non_existent_image(self):
+ # Return an error while trying to delete a non-existent image
+
+ non_existent_image_id = '11a22b9-12a9-5555-cc11-00ab112223fa'
+ self.assertRaises(exceptions.NotFound, self.client.delete_image,
+ non_existent_image_id)
+
+ @attr(type=['negative', 'gate'])
+ def test_delete_image_blank_id(self):
+ # Return an error while trying to delete an image with blank Id
+ self.assertRaises(exceptions.NotFound, self.client.delete_image, '')
+
+ @attr(type=['negative', 'gate'])
+ def test_delete_image_non_hex_string_id(self):
+ # Return an error while trying to delete an image with non hex id
+ image_id = '11a22b9-120q-5555-cc11-00ab112223gj'
+ self.assertRaises(exceptions.NotFound, self.client.delete_image,
+ image_id)
+
+ @attr(type=['negative', 'gate'])
+ def test_delete_image_negative_image_id(self):
+ # Return an error while trying to delete an image with negative id
+ self.assertRaises(exceptions.NotFound, self.client.delete_image, -1)
+
+ @attr(type=['negative', 'gate'])
+ def test_delete_image_id_is_over_35_character_limit(self):
+ # Return an error while trying to delete image with id over limit
+ self.assertRaises(exceptions.NotFound, self.client.delete_image,
+ '11a22b9-12a9-5555-cc11-00ab112223fa-3fac')
diff --git a/tempest/api/image/v2/test_images.py b/tempest/api/image/v2/test_images.py
index eb3535f..133bae0 100644
--- a/tempest/api/image/v2/test_images.py
+++ b/tempest/api/image/v2/test_images.py
@@ -20,6 +20,7 @@
import random
from tempest.api.image import base
+from tempest.common.utils import data_utils
from tempest import exceptions
from tempest.test import attr
@@ -42,29 +43,46 @@
'test', 'bare', 'wrong')
@attr(type='gate')
- def test_register_then_upload(self):
- # Register, then upload an image
- resp, body = self.create_image(name='New Name',
+ def test_register_upload_get_image_file(self):
+
+ """
+ Here we test these functionalities - Register image,
+ upload the image file, get image and get image file api's
+ """
+
+ image_name = data_utils.rand_name('image')
+ resp, body = self.create_image(name=image_name,
container_format='bare',
disk_format='raw',
visibility='public')
self.assertIn('id', body)
image_id = body.get('id')
self.assertIn('name', body)
- self.assertEqual('New Name', body.get('name'))
+ self.assertEqual(image_name, body['name'])
self.assertIn('visibility', body)
- self.assertTrue(body.get('visibility') == 'public')
+ self.assertEqual('public', body['visibility'])
self.assertIn('status', body)
- self.assertEqual('queued', body.get('status'))
+ self.assertEqual('queued', body['status'])
# Now try uploading an image file
- image_file = StringIO.StringIO(('*' * 1024))
+ file_content = '*' * 1024
+ image_file = StringIO.StringIO(file_content)
resp, body = self.client.store_image(image_id, image_file)
self.assertEqual(resp.status, 204)
- resp, body = self.client.get_image_metadata(image_id)
+
+ # Now try to get image details
+ resp, body = self.client.get_image(image_id)
+ self.assertEqual(200, resp.status)
+ self.assertEqual(image_id, body['id'])
+ self.assertEqual(image_name, body['name'])
self.assertIn('size', body)
self.assertEqual(1024, body.get('size'))
+ # Now try get image file
+ resp, body = self.client.get_image_file(image_id)
+ self.assertEqual(200, resp.status)
+ self.assertEqual(file_content, body)
+
class ListImagesTest(base.BaseV2ImageTest):
@@ -105,8 +123,3 @@
image_list = map(lambda x: x['id'], images_list)
for image in self.created_images:
self.assertIn(image, image_list)
-
- @attr(type=['negative', 'gate'])
- def test_get_image_meta_by_null_id(self):
- self.assertRaises(exceptions.NotFound,
- self.client.get_image_metadata, '')
diff --git a/tempest/api/image/v2/test_images_member.py b/tempest/api/image/v2/test_images_member.py
new file mode 100644
index 0000000..954c79d
--- /dev/null
+++ b/tempest/api/image/v2/test_images_member.py
@@ -0,0 +1,55 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.api.image import base
+from tempest.test import attr
+
+
+class ImagesMemberTest(base.BaseV2MemeberImageTest):
+ _interface = 'json'
+
+ @attr(type='gate')
+ def test_image_share_accept(self):
+ image_id = self._create_image()
+ resp, member = self.os_img_client.add_member(image_id,
+ self.alt_tenant_id)
+ self.assertEqual(member['member_id'], self.alt_tenant_id)
+ self.assertEqual(member['image_id'], image_id)
+ self.assertEqual(member['status'], 'pending')
+ self.assertNotIn(image_id, self._list_image_ids_as_alt())
+ self.alt_img_client.update_member_status(image_id,
+ self.alt_tenant_id,
+ 'accepted')
+ self.assertIn(image_id, self._list_image_ids_as_alt())
+ _, body = self.os_img_client.get_image_membership(image_id)
+ members = body['members']
+ member = members[0]
+ self.assertEqual(len(members), 1, str(members))
+ self.assertEqual(member['member_id'], self.alt_tenant_id)
+ self.assertEqual(member['image_id'], image_id)
+ self.assertEqual(member['status'], 'accepted')
+
+ @attr(type='gate')
+ def test_image_share_reject(self):
+ image_id = self._create_image()
+ resp, member = self.os_img_client.add_member(image_id,
+ self.alt_tenant_id)
+ self.assertEqual(member['member_id'], self.alt_tenant_id)
+ self.assertEqual(member['image_id'], image_id)
+ self.assertEqual(member['status'], 'pending')
+ self.assertNotIn(image_id, self._list_image_ids_as_alt())
+ self.alt_img_client.update_member_status(image_id,
+ self.alt_tenant_id,
+ 'rejected')
+ self.assertNotIn(image_id, self._list_image_ids_as_alt())
diff --git a/tempest/api/image/v2/test_images_member_negative.py b/tempest/api/image/v2/test_images_member_negative.py
new file mode 100644
index 0000000..3c17959
--- /dev/null
+++ b/tempest/api/image/v2/test_images_member_negative.py
@@ -0,0 +1,43 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.api.image import base
+from tempest import exceptions
+from tempest.test import attr
+
+
+class ImagesMemberNegativeTest(base.BaseV2MemeberImageTest):
+ _interface = 'json'
+
+ @attr(type=['negative', 'gate'])
+ def test_image_share_invalid_status(self):
+ image_id = self._create_image()
+ resp, member = self.os_img_client.add_member(image_id,
+ self.alt_tenant_id)
+ self.assertEqual(member['status'], 'pending')
+ self.assertRaises(exceptions.BadRequest,
+ self.alt_img_client.update_member_status,
+ image_id, self.alt_tenant_id, 'notavalidstatus')
+
+ @attr(type=['negative', 'gate'])
+ def test_image_share_owner_cannot_accept(self):
+ image_id = self._create_image()
+ resp, member = self.os_img_client.add_member(image_id,
+ self.alt_tenant_id)
+ self.assertEqual(member['status'], 'pending')
+ self.assertNotIn(image_id, self._list_image_ids_as_alt())
+ self.assertRaises(exceptions.Unauthorized,
+ self.os_img_client.update_member_status,
+ image_id, self.alt_tenant_id, 'accepted')
+ self.assertNotIn(image_id, self._list_image_ids_as_alt())
diff --git a/tempest/api/image/v2/test_images_negative.py b/tempest/api/image/v2/test_images_negative.py
new file mode 100644
index 0000000..5bdaa99
--- /dev/null
+++ b/tempest/api/image/v2/test_images_negative.py
@@ -0,0 +1,84 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 OpenStack Foundation
+# All Rights Reserved.
+# Copyright 2013 IBM Corp.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import uuid
+
+from tempest.api.image import base
+from tempest import exceptions
+from tempest.test import attr
+
+
+class ImagesNegativeTest(base.BaseV2ImageTest):
+
+ """
+ here we have -ve tests for get_image and delete_image api
+
+ Tests
+ ** get non-existent image
+ ** get image with image_id=NULL
+ ** get the deleted image
+ ** delete non-existent image
+ ** delete rimage with image_id=NULL
+ ** delete the deleted image
+ """
+
+ @attr(type=['negative', 'gate'])
+ def test_get_non_existent_image(self):
+ # get the non-existent image
+ non_existent_id = str(uuid.uuid4())
+ self.assertRaises(exceptions.NotFound, self.client.get_image,
+ non_existent_id)
+
+ @attr(type=['negative', 'gate'])
+ def test_get_image_null_id(self):
+ # get image with image_id = NULL
+ image_id = ""
+ self.assertRaises(exceptions.NotFound, self.client.get_image, image_id)
+
+ @attr(type=['negative', 'gate'])
+ def test_get_delete_deleted_image(self):
+ # get and delete the deleted image
+ # create and delete image
+ resp, body = self.client.create_image(name='test',
+ container_format='bare',
+ disk_format='raw')
+ image_id = body['id']
+ self.assertEqual(201, resp.status)
+ self.client.delete_image(image_id)
+ self.client.wait_for_resource_deletion(image_id)
+
+ # get the deleted image
+ self.assertRaises(exceptions.NotFound, self.client.get_image, image_id)
+
+ # delete the deleted image
+ self.assertRaises(exceptions.NotFound, self.client.delete_image,
+ image_id)
+
+ @attr(type=['negative', 'gate'])
+ def test_delete_non_existing_image(self):
+ # delete non-existent image
+ non_existent_image_id = str(uuid.uuid4())
+ self.assertRaises(exceptions.NotFound, self.client.delete_image,
+ non_existent_image_id)
+
+ @attr(type=['negative', 'gate'])
+ def test_delete_image_null_id(self):
+ # delete image with image_id=NULL
+ image_id = ""
+ self.assertRaises(exceptions.NotFound, self.client.delete_image,
+ image_id)
diff --git a/tempest/api/image/v2/test_images_tags.py b/tempest/api/image/v2/test_images_tags.py
new file mode 100644
index 0000000..e37e462
--- /dev/null
+++ b/tempest/api/image/v2/test_images_tags.py
@@ -0,0 +1,45 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.api.image import base
+from tempest.common.utils import data_utils
+from tempest.test import attr
+
+
+class ImagesTagsTest(base.BaseV2ImageTest):
+
+ @attr(type='gate')
+ def test_update_delete_tags_for_image(self):
+ resp, body = self.create_image(container_format='bare',
+ disk_format='raw',
+ visibility='public')
+ image_id = body['id']
+ tag = data_utils.rand_name('tag-')
+ self.addCleanup(self.client.delete_image, image_id)
+
+ # Creating image tag and verify it.
+ resp, body = self.client.add_image_tag(image_id, tag)
+ self.assertEqual(resp.status, 204)
+ resp, body = self.client.get_image(image_id)
+ self.assertEqual(resp.status, 200)
+ self.assertIn(tag, body['tags'])
+
+ # Deleting image tag and verify it.
+ resp = self.client.delete_image_tag(image_id, tag)
+ self.assertEqual(resp.status, 204)
+ resp, body = self.client.get_image(image_id)
+ self.assertEqual(resp.status, 200)
+ self.assertNotIn(tag, body['tags'])
diff --git a/tempest/api/image/v2/test_images_tags_negative.py b/tempest/api/image/v2/test_images_tags_negative.py
new file mode 100644
index 0000000..e0d84de
--- /dev/null
+++ b/tempest/api/image/v2/test_images_tags_negative.py
@@ -0,0 +1,46 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# 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 uuid
+
+from tempest.api.image import base
+from tempest.common.utils import data_utils
+from tempest import exceptions
+from tempest.test import attr
+
+
+class ImagesTagsNegativeTest(base.BaseV2ImageTest):
+
+ @attr(type=['negative', 'gate'])
+ def test_update_tags_for_non_existing_image(self):
+ # Update tag with non existing image.
+ tag = data_utils.rand_name('tag-')
+ non_exist_image = str(uuid.uuid4())
+ self.assertRaises(exceptions.NotFound, self.client.add_image_tag,
+ non_exist_image, tag)
+
+ @attr(type=['negative', 'gate'])
+ def test_delete_non_existing_tag(self):
+ # Delete non existing tag.
+ resp, body = self.create_image(container_format='bare',
+ disk_format='raw',
+ is_public=True,
+ )
+ image_id = body['id']
+ tag = data_utils.rand_name('non-exist-tag-')
+ self.addCleanup(self.client.delete_image, image_id)
+ self.assertRaises(exceptions.NotFound, self.client.delete_image_tag,
+ image_id, tag)
diff --git a/tempest/api/network/admin/__init__.py b/tempest/api/network/admin/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/api/network/admin/__init__.py
diff --git a/tempest/api/network/admin/test_l3_agent_scheduler.py b/tempest/api/network/admin/test_l3_agent_scheduler.py
new file mode 100644
index 0000000..9c187fd
--- /dev/null
+++ b/tempest/api/network/admin/test_l3_agent_scheduler.py
@@ -0,0 +1,64 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 IBM Corp.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.api.network import base
+from tempest.common.utils import data_utils
+from tempest.test import attr
+
+
+class L3AgentSchedulerJSON(base.BaseAdminNetworkTest):
+ _interface = 'json'
+
+ """
+ Tests the following operations in the Neutron API using the REST client for
+ Neutron:
+
+ List routers that the given L3 agent is hosting.
+ List L3 agents hosting the given router.
+
+ v2.0 of the Neutron API is assumed. It is also assumed that the following
+ options are defined in the [network] section of etc/tempest.conf:
+ """
+
+ @classmethod
+ def setUpClass(cls):
+ super(L3AgentSchedulerJSON, cls).setUpClass()
+
+ @attr(type='smoke')
+ def test_list_routers_on_l3_agent(self):
+ resp, body = self.admin_client.list_agents()
+ agents = body['agents']
+ for a in agents:
+ if a['agent_type'] == 'L3 agent':
+ agent = a
+ resp, body = self.admin_client.list_routers_on_l3_agent(
+ agent['id'])
+ self.assertEqual('200', resp['status'])
+
+ @attr(type='smoke')
+ def test_list_l3_agents_hosting_router(self):
+ name = data_utils.rand_name('router-')
+ resp, router = self.client.create_router(name)
+ self.assertEqual('201', resp['status'])
+ resp, body = self.admin_client.list_l3_agents_hosting_router(
+ router['router']['id'])
+ self.assertEqual('200', resp['status'])
+ resp, _ = self.client.delete_router(router['router']['id'])
+ self.assertEqual(204, resp.status)
+
+
+class L3AgentSchedulerXML(L3AgentSchedulerJSON):
+ _interface = 'xml'
diff --git a/tempest/api/network/base.py b/tempest/api/network/base.py
index cfac257..b222ae3 100644
--- a/tempest/api/network/base.py
+++ b/tempest/api/network/base.py
@@ -18,10 +18,13 @@
import netaddr
from tempest import clients
-from tempest.common.utils.data_utils import rand_name
+from tempest.common.utils import data_utils
from tempest import exceptions
+from tempest.openstack.common import log as logging
import tempest.test
+LOG = logging.getLogger(__name__)
+
class BaseNetworkTest(tempest.test.BaseTestCase):
@@ -56,25 +59,45 @@
cls.networks = []
cls.subnets = []
cls.ports = []
+ cls.routers = []
cls.pools = []
cls.vips = []
cls.members = []
cls.health_monitors = []
+ cls.vpnservices = []
@classmethod
def tearDownClass(cls):
+ # Clean up vpn services
+ for vpnservice in cls.vpnservices:
+ cls.client.delete_vpn_service(vpnservice['id'])
+ # Clean up routers
+ for router in cls.routers:
+ resp, body = cls.client.list_router_interfaces(router['id'])
+ interfaces = body['ports']
+ for i in interfaces:
+ cls.client.remove_router_interface_with_subnet_id(
+ router['id'], i['fixed_ips'][0]['subnet_id'])
+ cls.client.delete_router(router['id'])
+ # Clean up health monitors
for health_monitor in cls.health_monitors:
cls.client.delete_health_monitor(health_monitor['id'])
+ # Clean up members
for member in cls.members:
cls.client.delete_member(member['id'])
+ # Clean up vips
for vip in cls.vips:
cls.client.delete_vip(vip['id'])
+ # Clean up pools
for pool in cls.pools:
cls.client.delete_pool(pool['id'])
+ # Clean up ports
for port in cls.ports:
cls.client.delete_port(port['id'])
+ # Clean up subnets
for subnet in cls.subnets:
cls.client.delete_subnet(subnet['id'])
+ # Clean up networks
for network in cls.networks:
cls.client.delete_network(network['id'])
super(BaseNetworkTest, cls).tearDownClass()
@@ -82,7 +105,7 @@
@classmethod
def create_network(cls, network_name=None):
"""Wrapper utility that returns a test network."""
- network_name = network_name or rand_name('test-network-')
+ network_name = network_name or data_utils.rand_name('test-network-')
resp, body = cls.client.create_network(network_name)
network = body['network']
@@ -125,6 +148,21 @@
return port
@classmethod
+ def create_router(cls, router_name=None, admin_state_up=False,
+ external_network_id=None, enable_snat=None):
+ ext_gw_info = {}
+ if external_network_id:
+ ext_gw_info['network_id'] = external_network_id
+ if enable_snat:
+ ext_gw_info['enable_snat'] = enable_snat
+ resp, body = cls.client.create_router(
+ router_name, external_gateway_info=ext_gw_info,
+ admin_state_up=admin_state_up)
+ router = body['router']
+ cls.routers.append(router)
+ return router
+
+ @classmethod
def create_pool(cls, name, lb_method, protocol, subnet):
"""Wrapper utility that returns a test pool."""
resp, body = cls.client.create_pool(name, lb_method, protocol,
@@ -161,3 +199,35 @@
health_monitor = body['health_monitor']
cls.health_monitors.append(health_monitor)
return health_monitor
+
+ @classmethod
+ def create_router_interface(cls, router_id, subnet_id):
+ """Wrapper utility that returns a router interface."""
+ resp, interface = cls.client.add_router_interface_with_subnet_id(
+ router_id, subnet_id)
+
+ @classmethod
+ def create_vpnservice(cls, subnet_id, router_id):
+ """Wrapper utility that returns a test vpn service."""
+ resp, body = cls.client.create_vpn_service(
+ subnet_id, router_id, admin_state_up=True,
+ name=data_utils.rand_name("vpnservice-"))
+ vpnservice = body['vpnservice']
+ cls.vpnservices.append(vpnservice)
+ return vpnservice
+
+
+class BaseAdminNetworkTest(BaseNetworkTest):
+
+ @classmethod
+ def setUpClass(cls):
+ super(BaseAdminNetworkTest, cls).setUpClass()
+ admin_username = cls.config.compute_admin.username
+ admin_password = cls.config.compute_admin.password
+ admin_tenant = cls.config.compute_admin.tenant_name
+ if not (admin_username and admin_password and admin_tenant):
+ msg = ("Missing Administrative Network API credentials "
+ "in configuration.")
+ raise cls.skipException(msg)
+ cls.admin_manager = clients.AdminManager(interface=cls._interface)
+ cls.admin_client = cls.admin_manager.network_client
diff --git a/tempest/api/network/base_security_groups.py b/tempest/api/network/base_security_groups.py
new file mode 100644
index 0000000..5ab1748
--- /dev/null
+++ b/tempest/api/network/base_security_groups.py
@@ -0,0 +1,60 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 OpenStack Foundation
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.api.network import base
+from tempest.common.utils import data_utils
+
+
+class BaseSecGroupTest(base.BaseNetworkTest):
+
+ @classmethod
+ def setUpClass(cls):
+ super(BaseSecGroupTest, cls).setUpClass()
+
+ def _create_security_group(self):
+ # Create a security group
+ name = data_utils.rand_name('secgroup-')
+ resp, group_create_body = self.client.create_security_group(name)
+ self.assertEqual('201', resp['status'])
+ self.addCleanup(self._delete_security_group,
+ group_create_body['security_group']['id'])
+ self.assertEqual(group_create_body['security_group']['name'], name)
+ return group_create_body, name
+
+ def _delete_security_group(self, secgroup_id):
+ resp, _ = self.client.delete_security_group(secgroup_id)
+ self.assertEqual(204, resp.status)
+ # Asserting that the security group is not found in the list
+ # after deletion
+ resp, list_body = self.client.list_security_groups()
+ self.assertEqual('200', resp['status'])
+ secgroup_list = list()
+ for secgroup in list_body['security_groups']:
+ secgroup_list.append(secgroup['id'])
+ self.assertNotIn(secgroup_id, secgroup_list)
+
+ def _delete_security_group_rule(self, rule_id):
+ resp, _ = self.client.delete_security_group_rule(rule_id)
+ self.assertEqual(204, resp.status)
+ # Asserting that the security group is not found in the list
+ # after deletion
+ resp, list_body = self.client.list_security_group_rules()
+ self.assertEqual('200', resp['status'])
+ rules_list = list()
+ for rule in list_body['security_group_rules']:
+ rules_list.append(rule['id'])
+ self.assertNotIn(rule_id, rules_list)
diff --git a/tempest/api/network/common.py b/tempest/api/network/common.py
index c3fb821..43e7f68 100644
--- a/tempest/api/network/common.py
+++ b/tempest/api/network/common.py
@@ -56,7 +56,9 @@
class DeletableSubnet(DeletableResource):
- _router_ids = set()
+ def __init__(self, *args, **kwargs):
+ super(DeletableSubnet, self).__init__(*args, **kwargs)
+ self._router_ids = set()
def add_to_router(self, router_id):
self._router_ids.add(router_id)
diff --git a/tempest/api/network/test_floating_ips.py b/tempest/api/network/test_floating_ips.py
index ca2c879..35d4fa8 100644
--- a/tempest/api/network/test_floating_ips.py
+++ b/tempest/api/network/test_floating_ips.py
@@ -16,7 +16,7 @@
# under the License.
from tempest.api.network import base
-from tempest.common.utils.data_utils import rand_name
+from tempest.common.utils import data_utils
from tempest.test import attr
@@ -47,11 +47,9 @@
# Create network, subnet, router and add interface
cls.network = cls.create_network()
cls.subnet = cls.create_subnet(cls.network)
- resp, router = cls.client.create_router(
- rand_name('router-'),
- external_gateway_info={"network_id":
- cls.network_cfg.public_network_id})
- cls.router = router['router']
+ cls.router = cls.create_router(
+ data_utils.rand_name('router-'),
+ external_network_id=cls.network_cfg.public_network_id)
resp, _ = cls.client.add_router_interface_with_subnet_id(
cls.router['id'], cls.subnet['id'])
cls.port = list()
@@ -66,7 +64,6 @@
cls.subnet['id'])
for i in range(2):
cls.client.delete_port(cls.port[i]['id'])
- cls.client.delete_router(cls.router['id'])
super(FloatingIPTestJSON, cls).tearDownClass()
def _delete_floating_ip(self, floating_ip_id):
diff --git a/tempest/api/network/test_load_balancer.py b/tempest/api/network/test_load_balancer.py
index e3bf4e8..7e4ec37 100644
--- a/tempest/api/network/test_load_balancer.py
+++ b/tempest/api/network/test_load_balancer.py
@@ -16,7 +16,7 @@
# under the License.
from tempest.api.network import base
-from tempest.common.utils.data_utils import rand_name
+from tempest.common.utils import data_utils
from tempest.test import attr
@@ -45,8 +45,8 @@
cls.network = cls.create_network()
cls.name = cls.network['name']
cls.subnet = cls.create_subnet(cls.network)
- pool_name = rand_name('pool-')
- vip_name = rand_name('vip-')
+ pool_name = data_utils.rand_name('pool-')
+ vip_name = data_utils.rand_name('vip-')
cls.pool = cls.create_pool(pool_name, "ROUND_ROBIN",
"HTTP", cls.subnet)
cls.vip = cls.create_vip(vip_name, "HTTP", 80, cls.subnet, cls.pool)
@@ -68,8 +68,8 @@
def test_create_update_delete_pool_vip(self):
# Creates a vip
- name = rand_name('vip-')
- resp, body = self.client.create_pool(rand_name("pool-"),
+ name = data_utils.rand_name('vip-')
+ resp, body = self.client.create_pool(data_utils.rand_name("pool-"),
"ROUND_ROBIN", "HTTP",
self.subnet['id'])
pool = body['pool']
@@ -134,7 +134,7 @@
@attr(type='smoke')
def test_create_update_delete_member(self):
# Creates a member
- resp, body = self.client.create_member("10.0.9.46", 80,
+ resp, body = self.client.create_member("10.0.9.47", 80,
self.pool['id'])
self.assertEqual('201', resp['status'])
member = body['member']
diff --git a/tempest/api/network/test_networks.py b/tempest/api/network/test_networks.py
index f2df1ee..14c8500 100644
--- a/tempest/api/network/test_networks.py
+++ b/tempest/api/network/test_networks.py
@@ -18,7 +18,7 @@
import netaddr
from tempest.api.network import base
-from tempest.common.utils.data_utils import rand_name
+from tempest.common.utils import data_utils
from tempest import exceptions
from tempest.test import attr
@@ -66,7 +66,7 @@
@attr(type='smoke')
def test_create_update_delete_network_subnet(self):
# Creates a network
- name = rand_name('network-')
+ name = data_utils.rand_name('network-')
resp, body = self.client.create_network(name)
self.assertEqual('201', resp['status'])
network = body['network']
@@ -186,19 +186,19 @@
@attr(type=['negative', 'smoke'])
def test_show_non_existent_network(self):
- non_exist_id = rand_name('network')
+ non_exist_id = data_utils.rand_name('network')
self.assertRaises(exceptions.NotFound, self.client.show_network,
non_exist_id)
@attr(type=['negative', 'smoke'])
def test_show_non_existent_subnet(self):
- non_exist_id = rand_name('subnet')
+ non_exist_id = data_utils.rand_name('subnet')
self.assertRaises(exceptions.NotFound, self.client.show_subnet,
non_exist_id)
@attr(type=['negative', 'smoke'])
def test_show_non_existent_port(self):
- non_exist_id = rand_name('port')
+ non_exist_id = data_utils.rand_name('port')
self.assertRaises(exceptions.NotFound, self.client.show_port,
non_exist_id)
@@ -274,7 +274,8 @@
@attr(type='smoke')
def test_bulk_create_delete_network(self):
# Creates 2 networks in one request
- network_names = [rand_name('network-'), rand_name('network-')]
+ network_names = [data_utils.rand_name('network-'),
+ data_utils.rand_name('network-')]
resp, body = self.client.create_bulk_network(2, network_names)
created_networks = body['networks']
self.assertEqual('201', resp['status'])
@@ -299,7 +300,7 @@
names = []
networks = [self.network1['id'], self.network2['id']]
for i in range(len(networks)):
- names.append(rand_name('subnet-'))
+ names.append(data_utils.rand_name('subnet-'))
subnet_list = []
# TODO(raies): "for IPv6, version list [4, 6] will be used.
# and cidr for IPv6 will be of IPv6"
@@ -332,7 +333,7 @@
names = []
networks = [self.network1['id'], self.network2['id']]
for i in range(len(networks)):
- names.append(rand_name('port-'))
+ names.append(data_utils.rand_name('port-'))
port_list = []
state = [True, False]
for i in range(len(names)):
diff --git a/tempest/api/network/test_quotas.py b/tempest/api/network/test_quotas.py
index 51395ea..f7ba3cb 100644
--- a/tempest/api/network/test_quotas.py
+++ b/tempest/api/network/test_quotas.py
@@ -18,7 +18,7 @@
from tempest.api.network import base
from tempest import clients
-from tempest.common.utils.data_utils import rand_name
+from tempest.common.utils import data_utils
from tempest.test import attr
@@ -55,8 +55,8 @@
@attr(type='gate')
def test_quotas(self):
# Add a tenant to conduct the test
- test_tenant = rand_name('test_tenant_')
- test_description = rand_name('desc_')
+ test_tenant = data_utils.rand_name('test_tenant_')
+ test_description = data_utils.rand_name('desc_')
_, tenant = self.identity_admin_client.create_tenant(
name=test_tenant,
description=test_description)
diff --git a/tempest/api/network/test_routers.py b/tempest/api/network/test_routers.py
index 8b939fe..3cbe23f 100644
--- a/tempest/api/network/test_routers.py
+++ b/tempest/api/network/test_routers.py
@@ -16,11 +16,14 @@
# under the License.
from tempest.api.network import base
-from tempest.common.utils.data_utils import rand_name
+from tempest.common.utils import data_utils
from tempest.test import attr
-class RoutersTest(base.BaseNetworkTest):
+class RoutersTest(base.BaseAdminNetworkTest):
+ # NOTE(salv-orlando): This class inherits from BaseAdminNetworkTest
+ # as some router operations, such as enabling or disabling SNAT
+ # require admin credentials by default
_interface = 'json'
@classmethod
@@ -52,7 +55,9 @@
@attr(type='smoke')
def test_create_show_list_update_delete_router(self):
# Create a router
- name = rand_name('router-')
+ # NOTE(salv-orlando): Do not invoke self.create_router
+ # as we need to check the response code
+ name = data_utils.rand_name('router-')
resp, create_body = self.client.create_router(
name, external_gateway_info={
"network_id": self.network_cfg.public_network_id},
@@ -94,41 +99,133 @@
def test_add_remove_router_interface_with_subnet_id(self):
network = self.create_network()
subnet = self.create_subnet(network)
- name = rand_name('router-')
- resp, create_body = self.client.create_router(name)
- self.addCleanup(self.client.delete_router, create_body['router']['id'])
+ router = self.create_router(data_utils.rand_name('router-'))
# Add router interface with subnet id
resp, interface = self.client.add_router_interface_with_subnet_id(
- create_body['router']['id'], subnet['id'])
+ router['id'], subnet['id'])
self.assertEqual('200', resp['status'])
self.addCleanup(self._remove_router_interface_with_subnet_id,
- create_body['router']['id'], subnet['id'])
- self.assertTrue('subnet_id' in interface.keys())
- self.assertTrue('port_id' in interface.keys())
+ router['id'], subnet['id'])
+ self.assertIn('subnet_id', interface.keys())
+ self.assertIn('port_id', interface.keys())
# Verify router id is equal to device id in port details
resp, show_port_body = self.client.show_port(
interface['port_id'])
self.assertEqual(show_port_body['port']['device_id'],
- create_body['router']['id'])
+ router['id'])
@attr(type='smoke')
def test_add_remove_router_interface_with_port_id(self):
network = self.create_network()
self.create_subnet(network)
- name = rand_name('router-')
- resp, create_body = self.client.create_router(name)
- self.addCleanup(self.client.delete_router, create_body['router']['id'])
+ router = self.create_router(data_utils.rand_name('router-'))
resp, port_body = self.client.create_port(network['id'])
# add router interface to port created above
resp, interface = self.client.add_router_interface_with_port_id(
- create_body['router']['id'], port_body['port']['id'])
+ router['id'], port_body['port']['id'])
self.assertEqual('200', resp['status'])
self.addCleanup(self._remove_router_interface_with_port_id,
- create_body['router']['id'], port_body['port']['id'])
- self.assertTrue('subnet_id' in interface.keys())
- self.assertTrue('port_id' in interface.keys())
+ router['id'], port_body['port']['id'])
+ self.assertIn('subnet_id', interface.keys())
+ self.assertIn('port_id', interface.keys())
# Verify router id is equal to device id in port details
resp, show_port_body = self.client.show_port(
interface['port_id'])
self.assertEqual(show_port_body['port']['device_id'],
- create_body['router']['id'])
+ router['id'])
+
+ def _verify_router_gateway(self, router_id, exp_ext_gw_info=None):
+ resp, show_body = self.client.show_router(router_id)
+ self.assertEqual('200', resp['status'])
+ actual_ext_gw_info = show_body['router']['external_gateway_info']
+ if exp_ext_gw_info is None:
+ self.assertIsNone(actual_ext_gw_info)
+ return
+ # Verify only keys passed in exp_ext_gw_info
+ for k, v in exp_ext_gw_info.iteritems():
+ self.assertEqual(v, actual_ext_gw_info[k])
+
+ def _verify_gateway_port(self, router_id):
+ resp, list_body = self.admin_client.list_ports(
+ network_id=self.network_cfg.public_network_id,
+ device_id=router_id)
+ self.assertEqual(len(list_body['ports']), 1)
+ gw_port = list_body['ports'][0]
+ fixed_ips = gw_port['fixed_ips']
+ self.assertEqual(len(fixed_ips), 1)
+ resp, public_net_body = self.admin_client.show_network(
+ self.network_cfg.public_network_id)
+ public_subnet_id = public_net_body['network']['subnets'][0]
+ self.assertEqual(fixed_ips[0]['subnet_id'], public_subnet_id)
+
+ @attr(type='smoke')
+ def test_update_router_set_gateway(self):
+ router = self.create_router(data_utils.rand_name('router-'))
+ self.client.update_router(
+ router['id'],
+ external_gateway_info={
+ 'network_id': self.network_cfg.public_network_id})
+ # Verify operation - router
+ resp, show_body = self.client.show_router(router['id'])
+ self.assertEqual('200', resp['status'])
+ self._verify_router_gateway(
+ router['id'],
+ {'network_id': self.network_cfg.public_network_id})
+ self._verify_gateway_port(router['id'])
+
+ @attr(type='smoke')
+ def test_update_router_set_gateway_with_snat_explicit(self):
+ router = self.create_router(data_utils.rand_name('router-'))
+ self.admin_client.update_router_with_snat_gw_info(
+ router['id'],
+ external_gateway_info={
+ 'network_id': self.network_cfg.public_network_id,
+ 'enable_snat': True})
+ self._verify_router_gateway(
+ router['id'],
+ {'network_id': self.network_cfg.public_network_id,
+ 'enable_snat': True})
+ self._verify_gateway_port(router['id'])
+
+ @attr(type='smoke')
+ def test_update_router_set_gateway_without_snat(self):
+ router = self.create_router(data_utils.rand_name('router-'))
+ self.admin_client.update_router_with_snat_gw_info(
+ router['id'],
+ external_gateway_info={
+ 'network_id': self.network_cfg.public_network_id,
+ 'enable_snat': False})
+ self._verify_router_gateway(
+ router['id'],
+ {'network_id': self.network_cfg.public_network_id,
+ 'enable_snat': False})
+ self._verify_gateway_port(router['id'])
+
+ @attr(type='smoke')
+ def test_update_router_unset_gateway(self):
+ router = self.create_router(
+ data_utils.rand_name('router-'),
+ external_network_id=self.network_cfg.public_network_id)
+ self.client.update_router(router['id'], external_gateway_info={})
+ self._verify_router_gateway(router['id'])
+ # No gateway port expected
+ resp, list_body = self.admin_client.list_ports(
+ network_id=self.network_cfg.public_network_id,
+ device_id=router['id'])
+ self.assertFalse(list_body['ports'])
+
+ @attr(type='smoke')
+ def test_update_router_reset_gateway_without_snat(self):
+ router = self.create_router(
+ data_utils.rand_name('router-'),
+ external_network_id=self.network_cfg.public_network_id)
+ self.admin_client.update_router_with_snat_gw_info(
+ router['id'],
+ external_gateway_info={
+ 'network_id': self.network_cfg.public_network_id,
+ 'enable_snat': False})
+ self._verify_router_gateway(
+ router['id'],
+ {'network_id': self.network_cfg.public_network_id,
+ 'enable_snat': False})
+ self._verify_gateway_port(router['id'])
diff --git a/tempest/api/network/test_security_groups.py b/tempest/api/network/test_security_groups.py
index 60ca88a..9b0a3de 100644
--- a/tempest/api/network/test_security_groups.py
+++ b/tempest/api/network/test_security_groups.py
@@ -15,43 +15,13 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest.api.network import base
-from tempest.common.utils.data_utils import rand_name
-from tempest import exceptions
+from tempest.api.network import base_security_groups as base
from tempest.test import attr
-class SecGroupTest(base.BaseNetworkTest):
+class SecGroupTest(base.BaseSecGroupTest):
_interface = 'json'
- @classmethod
- def setUpClass(cls):
- super(SecGroupTest, cls).setUpClass()
-
- def _delete_security_group(self, secgroup_id):
- resp, _ = self.client.delete_security_group(secgroup_id)
- self.assertEqual(204, resp.status)
- # Asserting that the security group is not found in the list
- # after deletion
- resp, list_body = self.client.list_security_groups()
- self.assertEqual('200', resp['status'])
- secgroup_list = list()
- for secgroup in list_body['security_groups']:
- secgroup_list.append(secgroup['id'])
- self.assertNotIn(secgroup_id, secgroup_list)
-
- def _delete_security_group_rule(self, rule_id):
- resp, _ = self.client.delete_security_group_rule(rule_id)
- self.assertEqual(204, resp.status)
- # Asserting that the security group is not found in the list
- # after deletion
- resp, list_body = self.client.list_security_group_rules()
- self.assertEqual('200', resp['status'])
- rules_list = list()
- for rule in list_body['security_group_rules']:
- rules_list.append(rule['id'])
- self.assertNotIn(rule_id, rules_list)
-
@attr(type='smoke')
def test_list_security_groups(self):
# Verify the that security group belonging to tenant exist in list
@@ -66,14 +36,8 @@
self.assertIsNotNone(found, msg)
@attr(type='smoke')
- def test_create_show_delete_security_group_and_rule(self):
- # Create a security group
- name = rand_name('secgroup-')
- resp, group_create_body = self.client.create_security_group(name)
- self.assertEqual('201', resp['status'])
- self.addCleanup(self._delete_security_group,
- group_create_body['security_group']['id'])
- self.assertEqual(group_create_body['security_group']['name'], name)
+ def test_create_show_delete_security_group(self):
+ group_create_body, name = self._create_security_group()
# Show details of the created security group
resp, show_body = self.client.show_security_group(
@@ -88,14 +52,23 @@
for secgroup in list_body['security_groups']:
secgroup_list.append(secgroup['id'])
self.assertIn(group_create_body['security_group']['id'], secgroup_list)
- # No Update in security group
- # Create rule
- resp, rule_create_body = self.client.create_security_group_rule(
- group_create_body['security_group']['id']
- )
- self.assertEqual('201', resp['status'])
- self.addCleanup(self._delete_security_group_rule,
- rule_create_body['security_group_rule']['id'])
+
+ @attr(type='smoke')
+ def test_create_show_delete_security_group_rule(self):
+ group_create_body, _ = self._create_security_group()
+
+ # Create rules for each protocol
+ protocols = ['tcp', 'udp', 'icmp']
+ for protocol in protocols:
+ resp, rule_create_body = self.client.create_security_group_rule(
+ group_create_body['security_group']['id'],
+ protocol=protocol
+ )
+ self.assertEqual('201', resp['status'])
+ self.addCleanup(self._delete_security_group_rule,
+ rule_create_body['security_group_rule']['id']
+ )
+
# Show details of the created security rule
resp, show_rule_body = self.client.show_security_group_rule(
rule_create_body['security_group_rule']['id']
@@ -109,19 +82,6 @@
for rule in rule_list_body['security_group_rules']]
self.assertIn(rule_create_body['security_group_rule']['id'], rule_list)
- @attr(type=['negative', 'smoke'])
- def test_show_non_existent_security_group(self):
- non_exist_id = rand_name('secgroup-')
- self.assertRaises(exceptions.NotFound, self.client.show_security_group,
- non_exist_id)
-
- @attr(type=['negative', 'smoke'])
- def test_show_non_existent_security_group_rule(self):
- non_exist_id = rand_name('rule-')
- self.assertRaises(exceptions.NotFound,
- self.client.show_security_group_rule,
- non_exist_id)
-
class SecGroupTestXML(SecGroupTest):
_interface = 'xml'
diff --git a/tempest/api/network/test_security_groups_negative.py b/tempest/api/network/test_security_groups_negative.py
new file mode 100644
index 0000000..cb0c247
--- /dev/null
+++ b/tempest/api/network/test_security_groups_negative.py
@@ -0,0 +1,79 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 OpenStack Foundation
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.api.network import base_security_groups as base
+from tempest import exceptions
+from tempest.test import attr
+import uuid
+
+
+class NegativeSecGroupTest(base.BaseSecGroupTest):
+ _interface = 'json'
+
+ @attr(type=['negative', 'gate'])
+ def test_show_non_existent_security_group(self):
+ non_exist_id = str(uuid.uuid4())
+ self.assertRaises(exceptions.NotFound, self.client.show_security_group,
+ non_exist_id)
+
+ @attr(type=['negative', 'gate'])
+ def test_show_non_existent_security_group_rule(self):
+ non_exist_id = str(uuid.uuid4())
+ self.assertRaises(exceptions.NotFound,
+ self.client.show_security_group_rule,
+ non_exist_id)
+
+ @attr(type=['negative', 'gate'])
+ def test_delete_non_existent_security_group(self):
+ non_exist_id = str(uuid.uuid4())
+ self.assertRaises(exceptions.NotFound,
+ self.client.delete_security_group,
+ non_exist_id
+ )
+
+ @attr(type=['negative', 'gate'])
+ def test_create_security_group_rule_with_bad_protocol(self):
+ group_create_body, _ = self._create_security_group()
+
+ #Create rule with bad protocol name
+ pname = 'bad_protocol_name'
+ self.assertRaises(exceptions.BadRequest,
+ self.client.create_security_group_rule,
+ group_create_body['security_group']['id'],
+ protocol=pname)
+
+ @attr(type=['negative', 'gate'])
+ def test_create_security_group_rule_with_invalid_ports(self):
+ group_create_body, _ = self._create_security_group()
+
+ #Create rule with invalid ports
+ states = [(-16, 80, 'Invalid value for port -16'),
+ (80, 79, 'port_range_min must be <= port_range_max'),
+ (80, 65536, 'Invalid value for port 65536'),
+ (-16, 65536, 'Invalid value for port')]
+ for pmin, pmax, msg in states:
+ ex = self.assertRaises(exceptions.BadRequest,
+ self.client.create_security_group_rule,
+ group_create_body['security_group']['id'],
+ protocol='tcp',
+ port_range_min=pmin,
+ port_range_max=pmax)
+ self.assertIn(msg, str(ex))
+
+
+class NegativeSecGroupTestXML(NegativeSecGroupTest):
+ _interface = 'xml'
diff --git a/tempest/api/network/test_service_type_management.py b/tempest/api/network/test_service_type_management.py
new file mode 100644
index 0000000..ae03e96
--- /dev/null
+++ b/tempest/api/network/test_service_type_management.py
@@ -0,0 +1,30 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.api.network import base
+from tempest.test import attr
+
+
+class ServiceTypeManagementTestJSON(base.BaseNetworkTest):
+ _interface = 'json'
+
+ @attr(type='smoke')
+ def test_service_provider_list(self):
+ resp, body = self.client.list_service_providers()
+ self.assertEqual(resp['status'], '200')
+ self.assertIsInstance(body['service_providers'], list)
+
+
+class ServiceTypeManagementTestXML(ServiceTypeManagementTestJSON):
+ _interface = 'xml'
diff --git a/tempest/api/network/test_vpnaas_extensions.py b/tempest/api/network/test_vpnaas_extensions.py
new file mode 100644
index 0000000..9cbc7ac
--- /dev/null
+++ b/tempest/api/network/test_vpnaas_extensions.py
@@ -0,0 +1,96 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 OpenStack Foundation
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.api.network import base
+from tempest.common.utils import data_utils
+from tempest.test import attr
+
+
+class VPNaaSJSON(base.BaseNetworkTest):
+ _interface = 'json'
+
+ """
+ Tests the following operations in the Neutron API using the REST client for
+ Neutron:
+
+ List VPN Services
+ Show VPN Services
+ Create VPN Services
+ Update VPN Services
+ Delete VPN Services
+ """
+
+ @classmethod
+ def setUpClass(cls):
+ super(VPNaaSJSON, cls).setUpClass()
+ cls.network = cls.create_network()
+ cls.subnet = cls.create_subnet(cls.network)
+ cls.router = cls.create_router(data_utils.rand_name("router-"))
+ cls.create_router_interface(cls.router['id'], cls.subnet['id'])
+ cls.vpnservice = cls.create_vpnservice(cls.subnet['id'],
+ cls.router['id'])
+
+ @attr(type='smoke')
+ def test_list_vpn_services(self):
+ # Verify the VPN service exists in the list of all VPN services
+ resp, body = self.client.list_vpn_services()
+ self.assertEqual('200', resp['status'])
+ vpnservices = body['vpnservices']
+ self.assertIn(self.vpnservice['id'], [v['id'] for v in vpnservices])
+
+ @attr(type='smoke')
+ def test_create_update_delete_vpn_service(self):
+ # Creates a VPN service
+ name = data_utils.rand_name('vpn-service-')
+ resp, body = self.client.create_vpn_service(self.subnet['id'],
+ self.router['id'],
+ name=name,
+ admin_state_up=True)
+ self.assertEqual('201', resp['status'])
+ vpnservice = body['vpnservice']
+ # Assert if created vpnservices are not found in vpnservices list
+ resp, body = self.client.list_vpn_services()
+ vpn_services = [vs['id'] for vs in body['vpnservices']]
+ self.assertIsNotNone(vpnservice['id'])
+ self.assertIn(vpnservice['id'], vpn_services)
+
+ # TODO(raies): implement logic to update vpnservice
+ # VPNaaS client function to update is implemented.
+ # But precondition is that current state of vpnservice
+ # should be "ACTIVE" not "PENDING*"
+
+ # Verification of vpn service delete
+ resp, body = self.client.delete_vpn_service(vpnservice['id'])
+ self.assertEqual('204', resp['status'])
+ # Asserting if vpn service is found in the list after deletion
+ resp, body = self.client.list_vpn_services()
+ vpn_services = [vs['id'] for vs in body['vpnservices']]
+ self.assertNotIn(vpnservice['id'], vpn_services)
+
+ @attr(type='smoke')
+ def test_show_vpn_service(self):
+ # Verifies the details of a vpn service
+ resp, body = self.client.show_vpn_service(self.vpnservice['id'])
+ self.assertEqual('200', resp['status'])
+ vpnservice = body['vpnservice']
+ self.assertEqual(self.vpnservice['id'], vpnservice['id'])
+ self.assertEqual(self.vpnservice['name'], vpnservice['name'])
+ self.assertEqual(self.vpnservice['description'],
+ vpnservice['description'])
+ self.assertEqual(self.vpnservice['router_id'], vpnservice['router_id'])
+ self.assertEqual(self.vpnservice['subnet_id'], vpnservice['subnet_id'])
+ self.assertEqual(self.vpnservice['tenant_id'], vpnservice['tenant_id'])
diff --git a/tempest/api/object_storage/base.py b/tempest/api/object_storage/base.py
index c6639c2..e7cb806 100644
--- a/tempest/api/object_storage/base.py
+++ b/tempest/api/object_storage/base.py
@@ -18,6 +18,7 @@
from tempest.api.identity.base import DataGenerator
from tempest import clients
+from tempest.common import custom_matchers
from tempest.common import isolated_creds
from tempest import exceptions
import tempest.test
@@ -120,3 +121,12 @@
container_client.delete_container(cont)
except exceptions.NotFound:
pass
+
+ def assertHeaders(self, resp, target, method):
+ """
+ Common method to check the existence and the format of common response
+ headers
+ """
+ self.assertThat(resp, custom_matchers.ExistsAllResponseHeaders(
+ target, method))
+ self.assertThat(resp, custom_matchers.AreAllWellFormatted())
diff --git a/tempest/api/object_storage/test_account_quotas.py b/tempest/api/object_storage/test_account_quotas.py
index 65fe1ac..b4128e2 100644
--- a/tempest/api/object_storage/test_account_quotas.py
+++ b/tempest/api/object_storage/test_account_quotas.py
@@ -18,21 +18,20 @@
from tempest.api.object_storage import base
from tempest import clients
-from tempest.common.utils.data_utils import arbitrary_string
-from tempest.common.utils.data_utils import rand_name
-import tempest.config
+from tempest.common.utils import data_utils
+from tempest import config
from tempest import exceptions
from tempest.test import attr
class AccountQuotasTest(base.BaseObjectTest):
accounts_quotas_available = \
- tempest.config.TempestConfig().object_storage.accounts_quotas_available
+ config.TempestConfig().object_storage_feature_enabled.accounts_quotas
@classmethod
def setUpClass(cls):
super(AccountQuotasTest, cls).setUpClass()
- cls.container_name = rand_name(name="TestContainer")
+ cls.container_name = data_utils.rand_name(name="TestContainer")
cls.container_client.create_container(cls.container_name)
cls.data.setup_test_user()
@@ -102,8 +101,8 @@
"Account Quotas middleware not available")
@attr(type="smoke")
def test_upload_valid_object(self):
- object_name = rand_name(name="TestObject")
- data = arbitrary_string()
+ object_name = data_utils.rand_name(name="TestObject")
+ data = data_utils.arbitrary_string()
resp, _ = self.object_client.create_object(self.container_name,
object_name, data)
@@ -113,8 +112,8 @@
"Account Quotas middleware not available")
@attr(type=["negative", "smoke"])
def test_upload_large_object(self):
- object_name = rand_name(name="TestObject")
- data = arbitrary_string(30)
+ object_name = data_utils.rand_name(name="TestObject")
+ data = data_utils.arbitrary_string(30)
self.assertRaises(exceptions.OverLimit,
self.object_client.create_object,
self.container_name, object_name, data)
diff --git a/tempest/api/object_storage/test_account_services.py b/tempest/api/object_storage/test_account_services.py
index 1a3f775..12c823b 100644
--- a/tempest/api/object_storage/test_account_services.py
+++ b/tempest/api/object_storage/test_account_services.py
@@ -18,7 +18,7 @@
import random
from tempest.api.object_storage import base
-from tempest.common.utils.data_utils import rand_name
+from tempest.common.utils import data_utils
from tempest import exceptions
from tempest.test import attr
from tempest.test import HTTP_SUCCESS
@@ -30,7 +30,7 @@
super(AccountTest, cls).setUpClass()
cls.containers = []
for i in xrange(ord('a'), ord('f') + 1):
- name = rand_name(name='%s-' % chr(i))
+ name = data_utils.rand_name(name='%s-' % chr(i))
cls.container_client.create_container(name)
cls.containers.append(name)
cls.containers_count = len(cls.containers)
@@ -46,6 +46,7 @@
params = {'format': 'json'}
resp, container_list = \
self.account_client.list_account_containers(params=params)
+ self.assertHeaders(resp, 'Account', 'GET')
self.assertIsNotNone(container_list)
container_names = [c['name'] for c in container_list]
@@ -59,6 +60,8 @@
params = {'limit': limit}
resp, container_list = \
self.account_client.list_account_containers(params=params)
+ self.assertHeaders(resp, 'Account', 'GET')
+
self.assertEqual(len(container_list), limit)
@attr(type='smoke')
@@ -70,10 +73,15 @@
params = {'marker': self.containers[-1]}
resp, container_list = \
self.account_client.list_account_containers(params=params)
+ self.assertHeaders(resp, 'Account', 'GET')
+
self.assertEqual(len(container_list), 0)
+
params = {'marker': self.containers[self.containers_count / 2]}
resp, container_list = \
self.account_client.list_account_containers(params=params)
+ self.assertHeaders(resp, 'Account', 'GET')
+
self.assertEqual(len(container_list), self.containers_count / 2 - 1)
@attr(type='smoke')
@@ -85,10 +93,13 @@
params = {'end_marker': self.containers[0]}
resp, container_list = \
self.account_client.list_account_containers(params=params)
+ self.assertHeaders(resp, 'Account', 'GET')
self.assertEqual(len(container_list), 0)
+
params = {'end_marker': self.containers[self.containers_count / 2]}
resp, container_list = \
self.account_client.list_account_containers(params=params)
+ self.assertHeaders(resp, 'Account', 'GET')
self.assertEqual(len(container_list), self.containers_count / 2)
@attr(type='smoke')
@@ -101,16 +112,16 @@
'limit': limit}
resp, container_list = \
self.account_client.list_account_containers(params=params)
- self.assertLessEqual(len(container_list), limit)
+ self.assertHeaders(resp, 'Account', 'GET')
+
+ self.assertTrue(len(container_list) <= limit, str(container_list))
@attr(type='smoke')
def test_list_account_metadata(self):
# list all account metadata
resp, metadata = self.account_client.list_account_metadata()
self.assertIn(int(resp['status']), HTTP_SUCCESS)
- self.assertIn('x-account-object-count', resp)
- self.assertIn('x-account-container-count', resp)
- self.assertIn('x-account-bytes-used', resp)
+ self.assertHeaders(resp, 'Account', 'HEAD')
@attr(type='smoke')
def test_create_and_delete_account_metadata(self):
@@ -120,8 +131,11 @@
resp, _ = self.account_client.create_account_metadata(
metadata={header: data})
self.assertIn(int(resp['status']), HTTP_SUCCESS)
+ self.assertHeaders(resp, 'Account', 'POST')
resp, _ = self.account_client.list_account_metadata()
+ self.assertHeaders(resp, 'Account', 'HEAD')
+
self.assertIn('x-account-meta-' + header, resp)
self.assertEqual(resp['x-account-meta-' + header], data)
@@ -129,8 +143,10 @@
resp, _ = \
self.account_client.delete_account_metadata(metadata=[header])
self.assertIn(int(resp['status']), HTTP_SUCCESS)
+ self.assertHeaders(resp, 'Account', 'POST')
resp, _ = self.account_client.list_account_metadata()
+ self.assertHeaders(resp, 'Account', 'HEAD')
self.assertNotIn('x-account-meta-' + header, resp)
@attr(type=['negative', 'gate'])
diff --git a/tempest/api/object_storage/test_container_acl.py b/tempest/api/object_storage/test_container_acl.py
index de5307a..18000b9 100644
--- a/tempest/api/object_storage/test_container_acl.py
+++ b/tempest/api/object_storage/test_container_acl.py
@@ -16,7 +16,7 @@
# under the License.
from tempest.api.object_storage import base
-from tempest.common.utils.data_utils import rand_name
+from tempest.common.utils import data_utils
from tempest import exceptions
from tempest.test import attr
from tempest.test import HTTP_SUCCESS
@@ -39,7 +39,7 @@
def setUp(self):
super(ObjectTestACLs, self).setUp()
- self.container_name = rand_name(name='TestContainer')
+ self.container_name = data_utils.rand_name(name='TestContainer')
self.container_client.create_container(self.container_name)
def tearDown(self):
@@ -50,7 +50,7 @@
def test_write_object_without_using_creds(self):
# trying to create object with empty headers
# X-Auth-Token is not provided
- object_name = rand_name(name='Object')
+ object_name = data_utils.rand_name(name='Object')
self.assertRaises(exceptions.Unauthorized,
self.custom_object_client.create_object,
self.container_name, object_name, 'data')
@@ -58,7 +58,7 @@
@attr(type=['negative', 'gate'])
def test_delete_object_without_using_creds(self):
# create object
- object_name = rand_name(name='Object')
+ object_name = data_utils.rand_name(name='Object')
resp, _ = self.object_client.create_object(self.container_name,
object_name, 'data')
# trying to delete object with empty headers
@@ -71,7 +71,7 @@
def test_write_object_with_non_authorized_user(self):
# attempt to upload another file using non-authorized user
# User provided token is forbidden. ACL are not set
- object_name = rand_name(name='Object')
+ object_name = data_utils.rand_name(name='Object')
# trying to create object with non-authorized user
self.assertRaises(exceptions.Unauthorized,
self.custom_object_client.create_object,
@@ -82,7 +82,7 @@
def test_read_object_with_non_authorized_user(self):
# attempt to read object using non-authorized user
# User provided token is forbidden. ACL are not set
- object_name = rand_name(name='Object')
+ object_name = data_utils.rand_name(name='Object')
resp, _ = self.object_client.create_object(
self.container_name, object_name, 'data')
self.assertEqual(resp['status'], '201')
@@ -96,7 +96,7 @@
def test_delete_object_with_non_authorized_user(self):
# attempt to delete object using non-authorized user
# User provided token is forbidden. ACL are not set
- object_name = rand_name(name='Object')
+ object_name = data_utils.rand_name(name='Object')
resp, _ = self.object_client.create_object(
self.container_name, object_name, 'data')
self.assertEqual(resp['status'], '201')
@@ -116,7 +116,7 @@
metadata_prefix='')
self.assertIn(int(resp_meta['status']), HTTP_SUCCESS)
# create object
- object_name = rand_name(name='Object')
+ object_name = data_utils.rand_name(name='Object')
resp, _ = self.object_client.create_object(self.container_name,
object_name, 'data')
self.assertEqual(resp['status'], '201')
@@ -136,7 +136,7 @@
metadata_prefix='')
self.assertIn(int(resp_meta['status']), HTTP_SUCCESS)
# Trying to write the object without rights
- object_name = rand_name(name='Object')
+ object_name = data_utils.rand_name(name='Object')
self.assertRaises(exceptions.Unauthorized,
self.custom_object_client.create_object,
self.container_name,
@@ -154,7 +154,7 @@
metadata_prefix='')
self.assertIn(int(resp_meta['status']), HTTP_SUCCESS)
# create object
- object_name = rand_name(name='Object')
+ object_name = data_utils.rand_name(name='Object')
resp, _ = self.object_client.create_object(self.container_name,
object_name, 'data')
self.assertEqual(resp['status'], '201')
@@ -175,7 +175,7 @@
metadata_prefix='')
self.assertIn(int(resp_meta['status']), HTTP_SUCCESS)
# Trying to write the object with rights
- object_name = rand_name(name='Object')
+ object_name = data_utils.rand_name(name='Object')
resp, _ = self.custom_object_client.create_object(
self.container_name,
object_name, 'data',
@@ -194,7 +194,7 @@
metadata_prefix='')
self.assertIn(int(resp_meta['status']), HTTP_SUCCESS)
# Trying to write the object without write rights
- object_name = rand_name(name='Object')
+ object_name = data_utils.rand_name(name='Object')
self.assertRaises(exceptions.Unauthorized,
self.custom_object_client.create_object,
self.container_name,
@@ -213,7 +213,7 @@
metadata_prefix='')
self.assertIn(int(resp_meta['status']), HTTP_SUCCESS)
# create object
- object_name = rand_name(name='Object')
+ object_name = data_utils.rand_name(name='Object')
resp, _ = self.object_client.create_object(self.container_name,
object_name, 'data')
self.assertEqual(resp['status'], '201')
diff --git a/tempest/api/object_storage/test_container_quotas.py b/tempest/api/object_storage/test_container_quotas.py
index 31fe711..04536fe 100644
--- a/tempest/api/object_storage/test_container_quotas.py
+++ b/tempest/api/object_storage/test_container_quotas.py
@@ -18,8 +18,7 @@
import testtools
from tempest.api.object_storage import base
-from tempest.common.utils.data_utils import arbitrary_string
-from tempest.common.utils.data_utils import rand_name
+from tempest.common.utils import data_utils
from tempest import config
from tempest import exceptions
from tempest.test import attr
@@ -33,7 +32,7 @@
class ContainerQuotasTest(base.BaseObjectTest):
"""Attemps to test the perfect behavior of quotas in a container."""
container_quotas_available = \
- config.TempestConfig().object_storage.container_quotas_available
+ config.TempestConfig().object_storage_feature_enabled.container_quotas
def setUp(self):
"""Creates and sets a container with quotas.
@@ -46,7 +45,7 @@
Maximum object count of the container.
"""
super(ContainerQuotasTest, self).setUp()
- self.container_name = rand_name(name="TestContainer")
+ self.container_name = data_utils.rand_name(name="TestContainer")
self.container_client.create_container(self.container_name)
metadata = {"quota-bytes": str(QUOTA_BYTES),
"quota-count": str(QUOTA_COUNT), }
@@ -62,8 +61,8 @@
@attr(type="smoke")
def test_upload_valid_object(self):
"""Attempts to uploads an object smaller than the bytes quota."""
- object_name = rand_name(name="TestObject")
- data = arbitrary_string(QUOTA_BYTES)
+ object_name = data_utils.rand_name(name="TestObject")
+ data = data_utils.arbitrary_string(QUOTA_BYTES)
nbefore = self._get_bytes_used()
@@ -78,8 +77,8 @@
@attr(type="smoke")
def test_upload_large_object(self):
"""Attempts to upload an object lagger than the bytes quota."""
- object_name = rand_name(name="TestObject")
- data = arbitrary_string(QUOTA_BYTES + 1)
+ object_name = data_utils.rand_name(name="TestObject")
+ data = data_utils.arbitrary_string(QUOTA_BYTES + 1)
nbefore = self._get_bytes_used()
@@ -95,7 +94,7 @@
def test_upload_too_many_objects(self):
"""Attempts to upload many objects that exceeds the count limit."""
for _ in range(QUOTA_COUNT):
- name = rand_name(name="TestObject")
+ name = data_utils.rand_name(name="TestObject")
self.object_client.create_object(self.container_name, name, "")
nbefore = self._get_object_count()
diff --git a/tempest/api/object_storage/test_container_services.py b/tempest/api/object_storage/test_container_services.py
index 4b49d73..04de072 100644
--- a/tempest/api/object_storage/test_container_services.py
+++ b/tempest/api/object_storage/test_container_services.py
@@ -16,8 +16,7 @@
# under the License.
from tempest.api.object_storage import base
-from tempest.common.utils.data_utils import arbitrary_string
-from tempest.common.utils.data_utils import rand_name
+from tempest.common.utils import data_utils
from tempest.test import attr
from tempest.test import HTTP_SUCCESS
@@ -35,20 +34,23 @@
@attr(type='smoke')
def test_create_container(self):
- container_name = rand_name(name='TestContainer')
+ container_name = data_utils.rand_name(name='TestContainer')
resp, body = self.container_client.create_container(container_name)
self.containers.append(container_name)
self.assertIn(resp['status'], ('202', '201'))
+ self.assertHeaders(resp, 'Container', 'PUT')
@attr(type='smoke')
def test_delete_container(self):
# create a container
- container_name = rand_name(name='TestContainer')
+ container_name = data_utils.rand_name(name='TestContainer')
resp, _ = self.container_client.create_container(container_name)
self.containers.append(container_name)
# delete container
resp, _ = self.container_client.delete_container(container_name)
self.assertIn(int(resp['status']), HTTP_SUCCESS)
+ self.assertHeaders(resp, 'Container', 'DELETE')
+
self.containers.remove(container_name)
@attr(type='smoke')
@@ -56,17 +58,17 @@
# add metadata to an object
# create a container
- container_name = rand_name(name='TestContainer')
+ container_name = data_utils.rand_name(name='TestContainer')
resp, _ = self.container_client.create_container(container_name)
self.containers.append(container_name)
# create object
- object_name = rand_name(name='TestObject')
- data = arbitrary_string()
+ object_name = data_utils.rand_name(name='TestObject')
+ data = data_utils.arbitrary_string()
resp, _ = self.object_client.create_object(container_name,
object_name, data)
# set object metadata
- meta_key = rand_name(name='Meta-Test-')
- meta_value = rand_name(name='MetaValue-')
+ meta_key = data_utils.rand_name(name='Meta-Test-')
+ meta_value = data_utils.rand_name(name='MetaValue-')
orig_metadata = {meta_key: meta_value}
resp, _ = self.object_client.update_object_metadata(container_name,
object_name,
@@ -77,6 +79,8 @@
self.container_client.\
list_container_contents(container_name, params=params)
self.assertIn(int(resp['status']), HTTP_SUCCESS)
+ self.assertHeaders(resp, 'Container', 'GET')
+
self.assertIsNotNone(object_list)
object_names = [obj['name'] for obj in object_list]
@@ -87,7 +91,7 @@
# update/retrieve/delete container metadata
# create a container
- container_name = rand_name(name='TestContainer')
+ container_name = data_utils.rand_name(name='TestContainer')
resp, _ = self.container_client.create_container(container_name)
self.containers.append(container_name)
# update container metadata
@@ -98,11 +102,14 @@
self.container_client.update_container_metadata(container_name,
metadata=metadata)
self.assertIn(int(resp['status']), HTTP_SUCCESS)
+ self.assertHeaders(resp, 'Container', 'POST')
# list container metadata
resp, _ = self.container_client.list_container_metadata(
container_name)
self.assertIn(int(resp['status']), HTTP_SUCCESS)
+ self.assertHeaders(resp, 'Container', 'HEAD')
+
self.assertIn('x-container-meta-name', resp)
self.assertIn('x-container-meta-description', resp)
self.assertEqual(resp['x-container-meta-name'], 'Pictures')
@@ -113,9 +120,12 @@
container_name,
metadata=metadata.keys())
self.assertIn(int(resp['status']), HTTP_SUCCESS)
+ self.assertHeaders(resp, 'Container', 'POST')
# check if the metadata are no longer there
resp, _ = self.container_client.list_container_metadata(container_name)
self.assertIn(int(resp['status']), HTTP_SUCCESS)
+ self.assertHeaders(resp, 'Container', 'HEAD')
+
self.assertNotIn('x-container-meta-name', resp)
self.assertNotIn('x-container-meta-description', resp)
diff --git a/tempest/api/object_storage/test_container_staticweb.py b/tempest/api/object_storage/test_container_staticweb.py
index d07697a..9e405d6 100644
--- a/tempest/api/object_storage/test_container_staticweb.py
+++ b/tempest/api/object_storage/test_container_staticweb.py
@@ -15,8 +15,7 @@
# under the License.
from tempest.api.object_storage import base
-from tempest.common.utils.data_utils import arbitrary_string
-from tempest.common.utils.data_utils import rand_name
+from tempest.common.utils import data_utils
from tempest.test import attr
@@ -25,15 +24,15 @@
@classmethod
def setUpClass(cls):
super(StaticWebTest, cls).setUpClass()
- cls.container_name = rand_name(name="TestContainer")
+ cls.container_name = data_utils.rand_name(name="TestContainer")
# This header should be posted on the container before every test
cls.headers_public_read_acl = {'Read': '.r:*'}
# Create test container and create one object in it
cls.container_client.create_container(cls.container_name)
- cls.object_name = rand_name(name="TestObject")
- cls.object_data = arbitrary_string()
+ cls.object_name = data_utils.rand_name(name="TestObject")
+ cls.object_data = data_utils.arbitrary_string()
cls.object_client.create_object(cls.container_name,
cls.object_name,
cls.object_data)
diff --git a/tempest/api/object_storage/test_container_sync.py b/tempest/api/object_storage/test_container_sync.py
index ff9f7bf..dcfe219 100644
--- a/tempest/api/object_storage/test_container_sync.py
+++ b/tempest/api/object_storage/test_container_sync.py
@@ -18,7 +18,7 @@
import time
from tempest.api.object_storage import base
-from tempest.common.utils.data_utils import rand_name
+from tempest.common.utils import data_utils
from tempest.test import attr
from tempest.test import skip_because
@@ -37,9 +37,9 @@
int(container_sync_timeout / cls.container_sync_interval)
# define container and object clients
cls.clients = {}
- cls.clients[rand_name(name='TestContainerSync')] = \
+ cls.clients[data_utils.rand_name(name='TestContainerSync')] = \
(cls.container_client, cls.object_client)
- cls.clients[rand_name(name='TestContainerSync')] = \
+ cls.clients[data_utils.rand_name(name='TestContainerSync')] = \
(cls.container_client_alt, cls.object_client_alt)
for cont_name, client in cls.clients.items():
client[0].create_container(cont_name)
@@ -71,8 +71,8 @@
'Error installing X-Container-Sync-To '
'for the container "%s"' % (cont[0]))
# create object in container
- object_name = rand_name(name='TestSyncObject')
- data = object_name[::-1] # arbitrary_string()
+ object_name = data_utils.rand_name(name='TestSyncObject')
+ data = object_name[::-1] # data_utils.arbitrary_string()
resp, _ = obj_client[0].create_object(cont[0], object_name, data)
self.assertEqual(resp['status'], '201',
'Error creating the object "%s" in'
diff --git a/tempest/api/object_storage/test_crossdomain.py b/tempest/api/object_storage/test_crossdomain.py
new file mode 100644
index 0000000..0ae7e46
--- /dev/null
+++ b/tempest/api/object_storage/test_crossdomain.py
@@ -0,0 +1,82 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright (C) 2013 eNovance SAS <licensing@enovance.com>
+#
+# Author: Joe H. Rahme <joe.hakim.rahme@enovance.com>
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.api.object_storage import base
+from tempest import clients
+from tempest import config
+from tempest.test import attr
+from tempest.test import HTTP_SUCCESS
+
+
+class CrossdomainTest(base.BaseObjectTest):
+ crossdomain_available = \
+ config.TempestConfig().object_storage_feature_enabled.crossdomain
+
+ @classmethod
+ def setUpClass(cls):
+ super(CrossdomainTest, cls).setUpClass()
+
+ # skip this test if CORS isn't enabled in the conf file.
+ if not cls.crossdomain_available:
+ skip_msg = ("%s skipped as Crossdomain middleware not available"
+ % cls.__name__)
+ raise cls.skipException(skip_msg)
+
+ # creates a test user. The test user will set its base_url to the Swift
+ # endpoint and test the healthcheck feature.
+ cls.data.setup_test_user()
+
+ cls.os_test_user = clients.Manager(
+ cls.data.test_user,
+ cls.data.test_password,
+ cls.data.test_tenant)
+
+ cls.xml_start = '<?xml version="1.0"?>\n' \
+ '<!DOCTYPE cross-domain-policy SYSTEM ' \
+ '"http://www.adobe.com/xml/dtds/cross-domain-policy.' \
+ 'dtd" >\n<cross-domain-policy>\n'
+
+ cls.xml_end = "</cross-domain-policy>"
+
+ @classmethod
+ def tearDownClass(cls):
+ cls.data.teardown_all()
+ super(CrossdomainTest, cls).tearDownClass()
+
+ def setUp(self):
+ super(CrossdomainTest, self).setUp()
+
+ client = self.os_test_user.account_client
+ client._set_auth()
+ # Turning http://.../v1/foobar into http://.../
+ client.base_url = "/".join(client.base_url.split("/")[:-2])
+
+ def tearDown(self):
+ # clear the base_url for subsequent requests
+ self.os_test_user.account_client.base_url = None
+
+ super(CrossdomainTest, self).tearDown()
+
+ @attr('gate')
+ def test_get_crossdomain_policy(self):
+ resp, body = self.os_test_user.account_client.get("crossdomain.xml",
+ {})
+
+ self.assertIn(int(resp['status']), HTTP_SUCCESS)
+ self.assertTrue(body.startswith(self.xml_start) and
+ body.endswith(self.xml_end))
diff --git a/tempest/api/object_storage/test_healthcheck.py b/tempest/api/object_storage/test_healthcheck.py
new file mode 100644
index 0000000..798ea4f
--- /dev/null
+++ b/tempest/api/object_storage/test_healthcheck.py
@@ -0,0 +1,65 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright (C) 2013 eNovance SAS <licensing@enovance.com>
+#
+# Author: Joe H. Rahme <joe.hakim.rahme@enovance.com>
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+
+from tempest.api.object_storage import base
+from tempest import clients
+from tempest.test import attr
+from tempest.test import HTTP_SUCCESS
+
+
+class HealthcheckTest(base.BaseObjectTest):
+
+ @classmethod
+ def setUpClass(cls):
+ super(HealthcheckTest, cls).setUpClass()
+
+ # creates a test user. The test user will set its base_url to the Swift
+ # endpoint and test the healthcheck feature.
+ cls.data.setup_test_user()
+
+ cls.os_test_user = clients.Manager(
+ cls.data.test_user,
+ cls.data.test_password,
+ cls.data.test_tenant)
+
+ @classmethod
+ def tearDownClass(cls):
+ cls.data.teardown_all()
+ super(HealthcheckTest, cls).tearDownClass()
+
+ def setUp(self):
+ super(HealthcheckTest, self).setUp()
+ client = self.os_test_user.account_client
+ client._set_auth()
+
+ # Turning http://.../v1/foobar into http://.../
+ client.base_url = "/".join(client.base_url.split("/")[:-2])
+
+ def tearDown(self):
+ # clear the base_url for subsequent requests
+ self.os_test_user.account_client.base_url = None
+ super(HealthcheckTest, self).tearDown()
+
+ @attr('gate')
+ def test_get_healthcheck(self):
+
+ resp, _ = self.os_test_user.account_client.get("healthcheck", {})
+
+ # The status is expected to be 200
+ self.assertIn(int(resp['status']), HTTP_SUCCESS)
diff --git a/tempest/api/object_storage/test_object_expiry.py b/tempest/api/object_storage/test_object_expiry.py
index cb52d88..6fc3853 100644
--- a/tempest/api/object_storage/test_object_expiry.py
+++ b/tempest/api/object_storage/test_object_expiry.py
@@ -18,8 +18,7 @@
import time
from tempest.api.object_storage import base
-from tempest.common.utils.data_utils import arbitrary_string
-from tempest.common.utils.data_utils import rand_name
+from tempest.common.utils import data_utils
from tempest import exceptions
from tempest.test import attr
from tempest.test import skip_because
@@ -29,7 +28,7 @@
@classmethod
def setUpClass(cls):
super(ObjectExpiryTest, cls).setUpClass()
- cls.container_name = rand_name(name='TestContainer')
+ cls.container_name = data_utils.rand_name(name='TestContainer')
cls.container_client.create_container(cls.container_name)
@classmethod
@@ -49,8 +48,8 @@
# "X-Delete-At", after this test case works.
# create object
- object_name = rand_name(name='TestObject')
- data = arbitrary_string()
+ object_name = data_utils.rand_name(name='TestObject')
+ data = data_utils.arbitrary_string()
resp, _ = self.object_client.create_object(self.container_name,
object_name, data)
# update object metadata with expiry time of 3 seconds
diff --git a/tempest/api/object_storage/test_object_services.py b/tempest/api/object_storage/test_object_services.py
index 407c3ec..c7fdd0e 100644
--- a/tempest/api/object_storage/test_object_services.py
+++ b/tempest/api/object_storage/test_object_services.py
@@ -16,10 +16,11 @@
# under the License.
import hashlib
+import re
from tempest.api.object_storage import base
-from tempest.common.utils.data_utils import arbitrary_string
-from tempest.common.utils.data_utils import rand_name
+from tempest.common import custom_matchers
+from tempest.common.utils import data_utils
from tempest.test import attr
from tempest.test import HTTP_SUCCESS
@@ -28,7 +29,7 @@
@classmethod
def setUpClass(cls):
super(ObjectTest, cls).setUpClass()
- cls.container_name = rand_name(name='TestContainer')
+ cls.container_name = data_utils.rand_name(name='TestContainer')
cls.container_client.create_container(cls.container_name)
cls.containers = [cls.container_name]
@@ -51,52 +52,57 @@
@attr(type='smoke')
def test_create_object(self):
# create object
- object_name = rand_name(name='TestObject')
- data = arbitrary_string()
+ object_name = data_utils.rand_name(name='TestObject')
+ data = data_utils.arbitrary_string()
resp, _ = self.object_client.create_object(self.container_name,
object_name, data)
# create another object
- object_name = rand_name(name='TestObject')
- data = arbitrary_string()
+ object_name = data_utils.rand_name(name='TestObject')
+ data = data_utils.arbitrary_string()
resp, _ = self.object_client.create_object(self.container_name,
object_name, data)
self.assertEqual(resp['status'], '201')
+ self.assertHeaders(resp, 'Object', 'PUT')
@attr(type='smoke')
def test_delete_object(self):
# create object
- object_name = rand_name(name='TestObject')
- data = arbitrary_string()
+ object_name = data_utils.rand_name(name='TestObject')
+ data = data_utils.arbitrary_string()
resp, _ = self.object_client.create_object(self.container_name,
object_name, data)
# delete object
resp, _ = self.object_client.delete_object(self.container_name,
object_name)
self.assertIn(int(resp['status']), HTTP_SUCCESS)
+ self.assertHeaders(resp, 'Object', 'DELETE')
@attr(type='smoke')
def test_object_metadata(self):
# add metadata to storage object, test if metadata is retrievable
# create Object
- object_name = rand_name(name='TestObject')
- data = arbitrary_string()
+ object_name = data_utils.rand_name(name='TestObject')
+ data = data_utils.arbitrary_string()
resp, _ = self.object_client.create_object(self.container_name,
object_name, data)
# set object metadata
- meta_key = rand_name(name='test-')
- meta_value = rand_name(name='MetaValue-')
+ meta_key = data_utils.rand_name(name='test-')
+ meta_value = data_utils.rand_name(name='MetaValue-')
orig_metadata = {meta_key: meta_value}
resp, _ = self.object_client.update_object_metadata(
self.container_name, object_name, orig_metadata)
self.assertIn(int(resp['status']), HTTP_SUCCESS)
+ self.assertHeaders(resp, 'Object', 'POST')
# get object metadata
resp, resp_metadata = self.object_client.list_object_metadata(
self.container_name, object_name)
self.assertIn(int(resp['status']), HTTP_SUCCESS)
+ self.assertHeaders(resp, 'Object', 'HEAD')
+
actual_meta_key = 'x-object-meta-' + meta_key
- self.assertTrue(actual_meta_key in resp)
+ self.assertIn(actual_meta_key, resp)
self.assertEqual(resp[actual_meta_key], meta_value)
@attr(type='smoke')
@@ -104,29 +110,31 @@
# retrieve object's data (in response body)
# create object
- object_name = rand_name(name='TestObject')
- data = arbitrary_string()
+ object_name = data_utils.rand_name(name='TestObject')
+ data = data_utils.arbitrary_string()
resp, _ = self.object_client.create_object(self.container_name,
object_name, data)
# get object
resp, body = self.object_client.get_object(self.container_name,
object_name)
self.assertIn(int(resp['status']), HTTP_SUCCESS)
+ self.assertHeaders(resp, 'Object', 'GET')
+
self.assertEqual(body, data)
@attr(type='smoke')
def test_copy_object_in_same_container(self):
# create source object
- src_object_name = rand_name(name='SrcObject')
- src_data = arbitrary_string(size=len(src_object_name) * 2,
- base_text=src_object_name)
+ src_object_name = data_utils.rand_name(name='SrcObject')
+ src_data = data_utils.arbitrary_string(size=len(src_object_name) * 2,
+ base_text=src_object_name)
resp, _ = self.object_client.create_object(self.container_name,
src_object_name,
src_data)
# create destination object
- dst_object_name = rand_name(name='DstObject')
- dst_data = arbitrary_string(size=len(dst_object_name) * 3,
- base_text=dst_object_name)
+ dst_object_name = data_utils.rand_name(name='DstObject')
+ dst_data = data_utils.arbitrary_string(size=len(dst_object_name) * 3,
+ base_text=dst_object_name)
resp, _ = self.object_client.create_object(self.container_name,
dst_object_name,
dst_data)
@@ -134,6 +142,8 @@
resp, _ = self.object_client.copy_object_in_same_container(
self.container_name, src_object_name, dst_object_name)
self.assertEqual(resp['status'], '201')
+ self.assertHeaders(resp, 'Object', 'PUT')
+
# check data
resp, body = self.object_client.get_object(self.container_name,
dst_object_name)
@@ -144,8 +154,8 @@
# change the content type of an existing object
# create object
- object_name = rand_name(name='TestObject')
- data = arbitrary_string()
+ object_name = data_utils.rand_name(name='TestObject')
+ data = data_utils.arbitrary_string()
self.object_client.create_object(self.container_name,
object_name, data)
# get the old content type
@@ -157,6 +167,8 @@
resp, _ = self.object_client.copy_object_in_same_container(
self.container_name, object_name, object_name, metadata)
self.assertEqual(resp['status'], '201')
+ self.assertHeaders(resp, 'Object', 'PUT')
+
# check the content type
resp, _ = self.object_client.list_object_metadata(self.container_name,
object_name)
@@ -165,15 +177,15 @@
@attr(type='smoke')
def test_copy_object_2d_way(self):
# create source object
- src_object_name = rand_name(name='SrcObject')
- src_data = arbitrary_string(size=len(src_object_name) * 2,
- base_text=src_object_name)
+ src_object_name = data_utils.rand_name(name='SrcObject')
+ src_data = data_utils.arbitrary_string(size=len(src_object_name) * 2,
+ base_text=src_object_name)
resp, _ = self.object_client.create_object(self.container_name,
src_object_name, src_data)
# create destination object
- dst_object_name = rand_name(name='DstObject')
- dst_data = arbitrary_string(size=len(dst_object_name) * 3,
- base_text=dst_object_name)
+ dst_object_name = data_utils.rand_name(name='DstObject')
+ dst_data = data_utils.arbitrary_string(size=len(dst_object_name) * 3,
+ base_text=dst_object_name)
resp, _ = self.object_client.create_object(self.container_name,
dst_object_name, dst_data)
# copy source object to destination
@@ -181,6 +193,17 @@
src_object_name,
dst_object_name)
self.assertEqual(resp['status'], '201')
+ self.assertHeaders(resp, 'Object', 'COPY')
+
+ self.assertIn('last-modified', resp)
+ self.assertIn('x-copied-from', resp)
+ self.assertIn('x-copied-from-last-modified', resp)
+ self.assertNotEqual(len(resp['last-modified']), 0)
+ self.assertEqual(
+ resp['x-copied-from'],
+ self.container_name + "/" + src_object_name)
+ self.assertNotEqual(len(resp['x-copied-from-last-modified']), 0)
+
# check data
resp, body = self.object_client.get_object(self.container_name,
dst_object_name)
@@ -189,45 +212,50 @@
@attr(type='smoke')
def test_copy_object_across_containers(self):
# create a container to use as asource container
- src_container_name = rand_name(name='TestSourceContainer')
+ src_container_name = data_utils.rand_name(name='TestSourceContainer')
self.container_client.create_container(src_container_name)
self.containers.append(src_container_name)
# create a container to use as a destination container
- dst_container_name = rand_name(name='TestDestinationContainer')
+ dst_container_name = data_utils.rand_name(
+ name='TestDestinationContainer')
self.container_client.create_container(dst_container_name)
self.containers.append(dst_container_name)
# create object in source container
- object_name = rand_name(name='Object')
- data = arbitrary_string(size=len(object_name) * 2,
- base_text=object_name)
+ object_name = data_utils.rand_name(name='Object')
+ data = data_utils.arbitrary_string(size=len(object_name) * 2,
+ base_text=object_name)
resp, _ = self.object_client.create_object(src_container_name,
object_name, data)
# set object metadata
- meta_key = rand_name(name='test-')
- meta_value = rand_name(name='MetaValue-')
+ meta_key = data_utils.rand_name(name='test-')
+ meta_value = data_utils.rand_name(name='MetaValue-')
orig_metadata = {meta_key: meta_value}
resp, _ = self.object_client.update_object_metadata(src_container_name,
object_name,
orig_metadata)
self.assertIn(int(resp['status']), HTTP_SUCCESS)
+ self.assertHeaders(resp, 'Object', 'POST')
+
# copy object from source container to destination container
resp, _ = self.object_client.copy_object_across_containers(
src_container_name, object_name, dst_container_name,
object_name)
self.assertEqual(resp['status'], '201')
+ self.assertHeaders(resp, 'Object', 'PUT')
+
# check if object is present in destination container
resp, body = self.object_client.get_object(dst_container_name,
object_name)
self.assertEqual(body, data)
actual_meta_key = 'x-object-meta-' + meta_key
- self.assertTrue(actual_meta_key in resp)
+ self.assertIn(actual_meta_key, resp)
self.assertEqual(resp[actual_meta_key], meta_value)
@attr(type='gate')
def test_object_upload_in_segments(self):
# create object
- object_name = rand_name(name='LObject')
- data = arbitrary_string()
+ object_name = data_utils.rand_name(name='LObject')
+ data = data_utils.arbitrary_string()
segments = 10
data_segments = [data + str(i) for i in xrange(segments)]
# uploading segments
@@ -238,13 +266,34 @@
# creating a manifest file
metadata = {'X-Object-Manifest': '%s/%s/'
% (self.container_name, object_name)}
- self.object_client.create_object(self.container_name,
- object_name, data='')
+ resp, _ = self.object_client.create_object(self.container_name,
+ object_name, data='')
+ self.assertHeaders(resp, 'Object', 'PUT')
+
resp, _ = self.object_client.update_object_metadata(
self.container_name, object_name, metadata, metadata_prefix='')
+ self.assertHeaders(resp, 'Object', 'POST')
+
resp, _ = self.object_client.list_object_metadata(
self.container_name, object_name)
+
+ # Check only the existence of common headers with custom matcher
+ self.assertThat(resp, custom_matchers.ExistsAllResponseHeaders(
+ 'Object', 'HEAD'))
self.assertIn('x-object-manifest', resp)
+
+ # Etag value of a large object is enclosed in double-quotations.
+ # This is a special case, therefore the formats of response headers
+ # are checked without a custom matcher.
+ self.assertTrue(resp['etag'].startswith('\"'))
+ self.assertTrue(resp['etag'].endswith('\"'))
+ self.assertTrue(resp['etag'].strip('\"').isalnum())
+ self.assertTrue(re.match("^\d+\.?\d*\Z", resp['x-timestamp']))
+ self.assertNotEqual(len(resp['content-type']), 0)
+ self.assertTrue(re.match("^tx[0-9a-f]*-[0-9a-f]*$",
+ resp['x-trans-id']))
+ self.assertNotEqual(len(resp['date']), 0)
+ self.assertEqual(resp['accept-ranges'], 'bytes')
self.assertEqual(resp['x-object-manifest'],
'%s/%s/' % (self.container_name, object_name))
@@ -259,8 +308,8 @@
# Make a conditional request for an object using the If-None-Match
# header, it should get downloaded only if the local file is different,
# otherwise the response code should be 304 Not Modified
- object_name = rand_name(name='TestObject')
- data = arbitrary_string()
+ object_name = data_utils.rand_name(name='TestObject')
+ data = data_utils.arbitrary_string()
self.object_client.create_object(self.container_name,
object_name, data)
# local copy is identical, no download
@@ -270,18 +319,29 @@
resp, _ = self.object_client.get(url, headers=headers)
self.assertEqual(resp['status'], '304')
+ # When the file is not downloaded from Swift server, response does
+ # not contain 'X-Timestamp' header. This is the special case, therefore
+ # the existence of response headers is checked without custom matcher.
+ self.assertIn('content-type', resp)
+ self.assertIn('x-trans-id', resp)
+ self.assertIn('date', resp)
+ self.assertIn('accept-ranges', resp)
+ # Check only the format of common headers with custom matcher
+ self.assertThat(resp, custom_matchers.AreAllWellFormatted())
+
# local copy is different, download
local_data = "something different"
md5 = hashlib.md5(local_data).hexdigest()
headers = {'If-None-Match': md5}
resp, body = self.object_client.get(url, headers=headers)
self.assertIn(int(resp['status']), HTTP_SUCCESS)
+ self.assertHeaders(resp, 'Object', 'GET')
class PublicObjectTest(base.BaseObjectTest):
def setUp(self):
super(PublicObjectTest, self).setUp()
- self.container_name = rand_name(name='TestContainer')
+ self.container_name = data_utils.rand_name(name='TestContainer')
self.container_client.create_container(self.container_name)
def tearDown(self):
@@ -298,24 +358,31 @@
resp_meta, body = self.container_client.update_container_metadata(
self.container_name, metadata=cont_headers, metadata_prefix='')
self.assertIn(int(resp_meta['status']), HTTP_SUCCESS)
+ self.assertHeaders(resp_meta, 'Container', 'POST')
+
# create object
- object_name = rand_name(name='Object')
- data = arbitrary_string(size=len(object_name),
- base_text=object_name)
+ object_name = data_utils.rand_name(name='Object')
+ data = data_utils.arbitrary_string(size=len(object_name),
+ base_text=object_name)
resp, _ = self.object_client.create_object(self.container_name,
object_name, data)
self.assertEqual(resp['status'], '201')
+ self.assertHeaders(resp, 'Object', 'PUT')
# list container metadata
resp_meta, _ = self.container_client.list_container_metadata(
self.container_name)
- self.assertIn(int(resp['status']), HTTP_SUCCESS)
+ self.assertIn(int(resp_meta['status']), HTTP_SUCCESS)
+ self.assertHeaders(resp_meta, 'Container', 'HEAD')
+
self.assertIn('x-container-read', resp_meta)
self.assertEqual(resp_meta['x-container-read'], '.r:*,.rlistings')
# trying to get object with empty headers as it is public readable
resp, body = self.custom_object_client.get_object(
self.container_name, object_name, metadata={})
+ self.assertHeaders(resp, 'Object', 'GET')
+
self.assertEqual(body, data)
@attr(type='smoke')
@@ -327,19 +394,23 @@
self.container_name, metadata=cont_headers,
metadata_prefix='')
self.assertIn(int(resp_meta['status']), HTTP_SUCCESS)
+ self.assertHeaders(resp_meta, 'Container', 'POST')
# create object
- object_name = rand_name(name='Object')
- data = arbitrary_string(size=len(object_name) * 1,
- base_text=object_name)
+ object_name = data_utils.rand_name(name='Object')
+ data = data_utils.arbitrary_string(size=len(object_name) * 1,
+ base_text=object_name)
resp, _ = self.object_client.create_object(self.container_name,
object_name, data)
self.assertEqual(resp['status'], '201')
+ self.assertHeaders(resp, 'Object', 'PUT')
# list container metadata
resp, _ = self.container_client.list_container_metadata(
self.container_name)
self.assertIn(int(resp['status']), HTTP_SUCCESS)
+ self.assertHeaders(resp, 'Container', 'HEAD')
+
self.assertIn('x-container-read', resp)
self.assertEqual(resp['x-container-read'], '.r:*,.rlistings')
@@ -350,4 +421,6 @@
resp, body = self.custom_object_client.get_object(
self.container_name, object_name,
metadata=headers)
+ self.assertHeaders(resp, 'Object', 'GET')
+
self.assertEqual(body, data)
diff --git a/tempest/api/object_storage/test_object_temp_url.py b/tempest/api/object_storage/test_object_temp_url.py
index 0fd5499..77f3a53 100644
--- a/tempest/api/object_storage/test_object_temp_url.py
+++ b/tempest/api/object_storage/test_object_temp_url.py
@@ -21,8 +21,7 @@
import urlparse
from tempest.api.object_storage import base
-from tempest.common.utils.data_utils import arbitrary_string
-from tempest.common.utils.data_utils import rand_name
+from tempest.common.utils import data_utils
from tempest import exceptions
from tempest.test import attr
from tempest.test import HTTP_SUCCESS
@@ -33,7 +32,7 @@
@classmethod
def setUpClass(cls):
super(ObjectTempUrlTest, cls).setUpClass()
- cls.container_name = rand_name(name='TestContainer')
+ cls.container_name = data_utils.rand_name(name='TestContainer')
cls.container_client.create_container(cls.container_name)
cls.containers = [cls.container_name]
@@ -66,9 +65,9 @@
self.key)
# create object
- self.object_name = rand_name(name='ObjectTemp')
- self.data = arbitrary_string(size=len(self.object_name),
- base_text=self.object_name)
+ self.object_name = data_utils.rand_name(name='ObjectTemp')
+ self.data = data_utils.arbitrary_string(size=len(self.object_name),
+ base_text=self.object_name)
self.object_client.create_object(self.container_name,
self.object_name, self.data)
@@ -111,8 +110,9 @@
@attr(type='gate')
def test_put_object_using_temp_url(self):
# make sure the metadata has been set
- new_data = arbitrary_string(size=len(self.object_name),
- base_text=rand_name(name="random"))
+ new_data = data_utils.arbitrary_string(
+ size=len(self.object_name),
+ base_text=data_utils.rand_name(name="random"))
EXPIRATION_TIME = 10000 # high to ensure the test finishes.
expires = int(time.time() + EXPIRATION_TIME)
diff --git a/tempest/api/object_storage/test_object_version.py b/tempest/api/object_storage/test_object_version.py
index 2b75b77..c47e1b6 100644
--- a/tempest/api/object_storage/test_object_version.py
+++ b/tempest/api/object_storage/test_object_version.py
@@ -16,7 +16,7 @@
# under the License.
from tempest.api.object_storage import base
-from tempest.common.utils.data_utils import rand_name
+from tempest.common.utils import data_utils
from tempest.test import attr
@@ -44,14 +44,14 @@
@attr(type='smoke')
def test_versioned_container(self):
# create container
- vers_container_name = rand_name(name='TestVersionContainer')
+ vers_container_name = data_utils.rand_name(name='TestVersionContainer')
resp, body = self.container_client.create_container(
vers_container_name)
self.containers.append(vers_container_name)
self.assertIn(resp['status'], ('202', '201'))
self.assertContainer(vers_container_name, '0', '0', 'Missing Header')
- base_container_name = rand_name(name='TestBaseContainer')
+ base_container_name = data_utils.rand_name(name='TestBaseContainer')
headers = {'X-versions-Location': vers_container_name}
resp, body = self.container_client.create_container(
base_container_name,
@@ -61,7 +61,7 @@
self.assertIn(resp['status'], ('202', '201'))
self.assertContainer(base_container_name, '0', '0',
vers_container_name)
- object_name = rand_name(name='TestObject')
+ object_name = data_utils.rand_name(name='TestObject')
# create object
resp, _ = self.object_client.create_object(base_container_name,
object_name, '1')
diff --git a/tempest/api/orchestration/base.py b/tempest/api/orchestration/base.py
index 7c72991..f3ef99f 100644
--- a/tempest/api/orchestration/base.py
+++ b/tempest/api/orchestration/base.py
@@ -15,7 +15,7 @@
import time
from tempest import clients
-from tempest.common.utils.data_utils import rand_name
+from tempest.common.utils import data_utils
from tempest.openstack.common import log as logging
import tempest.test
@@ -99,7 +99,7 @@
@classmethod
def _create_keypair(cls, name_start='keypair-heat-'):
- kp_name = rand_name(name_start)
+ kp_name = data_utils.rand_name(name_start)
resp, body = cls.keypairs_client.create_keypair(kp_name)
cls.keypairs.append(kp_name)
return body
diff --git a/tempest/api/orchestration/stacks/test_limits.py b/tempest/api/orchestration/stacks/test_limits.py
index aa59581..d294c7a 100644
--- a/tempest/api/orchestration/stacks/test_limits.py
+++ b/tempest/api/orchestration/stacks/test_limits.py
@@ -15,7 +15,7 @@
import logging
from tempest.api.orchestration import base
-from tempest.common.utils.data_utils import rand_name
+from tempest.common.utils import data_utils
from tempest import exceptions
from tempest.test import attr
@@ -30,7 +30,7 @@
def setUpClass(cls):
super(TestServerStackLimits, cls).setUpClass()
cls.client = cls.orchestration_client
- cls.stack_name = rand_name('heat')
+ cls.stack_name = data_utils.rand_name('heat')
@attr(type='gate')
def test_exceed_max_template_size_fails(self):
diff --git a/tempest/api/orchestration/stacks/test_neutron_resources.py b/tempest/api/orchestration/stacks/test_neutron_resources.py
index 174c82a..c86edf0 100644
--- a/tempest/api/orchestration/stacks/test_neutron_resources.py
+++ b/tempest/api/orchestration/stacks/test_neutron_resources.py
@@ -17,7 +17,7 @@
from tempest.api.orchestration import base
from tempest import clients
-from tempest.common.utils.data_utils import rand_name
+from tempest.common.utils import data_utils
from tempest.test import attr
@@ -101,7 +101,7 @@
if not cls.config.service_available.neutron:
raise cls.skipException("Neutron support is required")
cls.network_client = os.network_client
- cls.stack_name = rand_name('heat')
+ cls.stack_name = data_utils.rand_name('heat')
cls.keypair_name = (cls.orchestration_cfg.keypair_name or
cls._create_keypair()['name'])
cls.external_router_id = cls._get_external_router_id()
diff --git a/tempest/api/orchestration/stacks/test_non_empty_stack.py b/tempest/api/orchestration/stacks/test_non_empty_stack.py
index 0ecc5ff..35a7326 100644
--- a/tempest/api/orchestration/stacks/test_non_empty_stack.py
+++ b/tempest/api/orchestration/stacks/test_non_empty_stack.py
@@ -15,7 +15,7 @@
import logging
from tempest.api.orchestration import base
-from tempest.common.utils.data_utils import rand_name
+from tempest.common.utils import data_utils
from tempest.test import attr
@@ -76,7 +76,7 @@
if not cls.orchestration_cfg.image_ref:
raise cls.skipException("No image available to test")
cls.client = cls.orchestration_client
- cls.stack_name = rand_name('heat')
+ cls.stack_name = data_utils.rand_name('heat')
keypair_name = (cls.orchestration_cfg.keypair_name or
cls._create_keypair()['name'])
diff --git a/tempest/api/orchestration/stacks/test_server_cfn_init.py b/tempest/api/orchestration/stacks/test_server_cfn_init.py
index ea0bff5..3c2a2d2 100644
--- a/tempest/api/orchestration/stacks/test_server_cfn_init.py
+++ b/tempest/api/orchestration/stacks/test_server_cfn_init.py
@@ -16,7 +16,7 @@
import testtools
from tempest.api.orchestration import base
-from tempest.common.utils.data_utils import rand_name
+from tempest.common.utils import data_utils
from tempest.common.utils.linux.remote_client import RemoteClient
import tempest.config
from tempest.openstack.common import log as logging
@@ -132,7 +132,7 @@
raise cls.skipException("No image available to test")
cls.client = cls.orchestration_client
- stack_name = rand_name('heat')
+ stack_name = data_utils.rand_name('heat')
if cls.orchestration_cfg.keypair_name:
keypair_name = cls.orchestration_cfg.keypair_name
else:
diff --git a/tempest/api/orchestration/stacks/test_stacks.py b/tempest/api/orchestration/stacks/test_stacks.py
index 4bda5ab..0b7883d 100644
--- a/tempest/api/orchestration/stacks/test_stacks.py
+++ b/tempest/api/orchestration/stacks/test_stacks.py
@@ -13,7 +13,7 @@
# under the License.
from tempest.api.orchestration import base
-from tempest.common.utils.data_utils import rand_name
+from tempest.common.utils import data_utils
from tempest.openstack.common import log as logging
from tempest.test import attr
@@ -39,7 +39,7 @@
@attr(type='smoke')
def test_stack_crud_no_resources(self):
- stack_name = rand_name('heat')
+ stack_name = data_utils.rand_name('heat')
# create the stack
stack_identifier = self.create_stack(
diff --git a/tempest/api/orchestration/stacks/test_templates.py b/tempest/api/orchestration/stacks/test_templates.py
index 6a7c541..2589632 100644
--- a/tempest/api/orchestration/stacks/test_templates.py
+++ b/tempest/api/orchestration/stacks/test_templates.py
@@ -15,7 +15,7 @@
import logging
from tempest.api.orchestration import base
-from tempest.common.utils.data_utils import rand_name
+from tempest.common.utils import data_utils
from tempest import exceptions
from tempest.test import attr
@@ -41,7 +41,7 @@
def setUpClass(cls):
super(TemplateYAMLTestJSON, cls).setUpClass()
cls.client = cls.orchestration_client
- cls.stack_name = rand_name('heat')
+ cls.stack_name = data_utils.rand_name('heat')
cls.stack_identifier = cls.create_stack(cls.stack_name, cls.template)
cls.client.wait_for_stack_status(cls.stack_identifier,
'CREATE_COMPLETE')
diff --git a/tempest/api/volume/admin/test_multi_backend.py b/tempest/api/volume/admin/test_multi_backend.py
index 797aa71..03e8469 100644
--- a/tempest/api/volume/admin/test_multi_backend.py
+++ b/tempest/api/volume/admin/test_multi_backend.py
@@ -13,7 +13,7 @@
# under the License.
from tempest.api.volume import base
-from tempest.common.utils.data_utils import rand_name
+from tempest.common.utils import data_utils
from tempest.openstack.common import log as logging
from tempest.services.volume.json.admin import volume_types_client
from tempest.services.volume.json import volumes_client
@@ -28,7 +28,8 @@
@classmethod
def setUpClass(cls):
super(VolumeMultiBackendTest, cls).setUpClass()
- if not cls.config.volume.multi_backend_enabled:
+ if not cls.config.volume_feature_enabled.multi_backend:
+ cls.tearDownClass()
raise cls.skipException("Cinder multi-backend feature disabled")
cls.backend1_name = cls.config.volume.backend1_name
@@ -54,8 +55,8 @@
cls.volume_id_list = []
try:
# Volume/Type creation (uses backend1_name)
- type1_name = rand_name('Type-')
- vol1_name = rand_name('Volume-')
+ type1_name = data_utils.rand_name('Type-')
+ vol1_name = data_utils.rand_name('Volume-')
extra_specs1 = {"volume_backend_name": cls.backend1_name}
resp, cls.type1 = cls.type_client.create_volume_type(
type1_name, extra_specs=extra_specs1)
@@ -69,8 +70,8 @@
if cls.backend1_name != cls.backend2_name:
# Volume/Type creation (uses backend2_name)
- type2_name = rand_name('Type-')
- vol2_name = rand_name('Volume-')
+ type2_name = data_utils.rand_name('Type-')
+ vol2_name = data_utils.rand_name('Volume-')
extra_specs2 = {"volume_backend_name": cls.backend2_name}
resp, cls.type2 = cls.type_client.create_volume_type(
type2_name, extra_specs=extra_specs2)
@@ -89,12 +90,14 @@
@classmethod
def tearDownClass(cls):
# volumes deletion
- for volume_id in cls.volume_id_list:
+ volume_id_list = getattr(cls, 'volume_id_list', [])
+ for volume_id in volume_id_list:
cls.volume_client.delete_volume(volume_id)
cls.volume_client.wait_for_resource_deletion(volume_id)
# volume types deletion
- for volume_type_id in cls.volume_type_id_list:
+ volume_type_id_list = getattr(cls, 'volume_type_id_list', [])
+ for volume_type_id in volume_type_id_list:
cls.type_client.delete_volume_type(volume_type_id)
super(VolumeMultiBackendTest, cls).tearDownClass()
diff --git a/tempest/api/volume/admin/test_snapshots_actions.py b/tempest/api/volume/admin/test_snapshots_actions.py
new file mode 100644
index 0000000..5e838e5
--- /dev/null
+++ b/tempest/api/volume/admin/test_snapshots_actions.py
@@ -0,0 +1,110 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 Huawei Technologies Co.,LTD
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.api.volume.base import BaseVolumeAdminTest
+from tempest.common.utils import data_utils
+from tempest.test import attr
+
+
+class SnapshotsActionsTest(BaseVolumeAdminTest):
+ _interface = "json"
+
+ @classmethod
+ def setUpClass(cls):
+ super(SnapshotsActionsTest, cls).setUpClass()
+ cls.client = cls.snapshots_client
+
+ # Create admin volume client
+ cls.admin_snapshots_client = cls.os_adm.snapshots_client
+
+ # Create a test shared volume for tests
+ vol_name = data_utils.rand_name(cls.__name__ + '-Volume-')
+ resp_vol, cls.volume = \
+ cls.volumes_client.create_volume(size=1, display_name=vol_name)
+ cls.volumes_client.wait_for_volume_status(cls.volume['id'],
+ 'available')
+
+ # Create a test shared snapshot for tests
+ snap_name = data_utils.rand_name(cls.__name__ + '-Snapshot-')
+ resp_snap, cls.snapshot = \
+ cls.client.create_snapshot(cls.volume['id'],
+ display_name=snap_name)
+ cls.client.wait_for_snapshot_status(cls.snapshot['id'],
+ 'available')
+
+ @classmethod
+ def tearDownClass(cls):
+ # Delete the test snapshot
+ cls.client.delete_snapshot(cls.snapshot['id'])
+ cls.client.wait_for_resource_deletion(cls.snapshot['id'])
+
+ # Delete the test volume
+ cls.volumes_client.delete_volume(cls.volume['id'])
+ cls.volumes_client.wait_for_resource_deletion(cls.volume['id'])
+
+ super(SnapshotsActionsTest, cls).tearDownClass()
+
+ def tearDown(self):
+ # Set snapshot's status to available after test
+ status = 'available'
+ snapshot_id = self.snapshot['id']
+ self.admin_snapshots_client.reset_snapshot_status(snapshot_id,
+ status)
+ super(SnapshotsActionsTest, self).tearDown()
+
+ def _get_progress_alias(self):
+ return 'os-extended-snapshot-attributes:progress'
+
+ @attr(type='gate')
+ def test_reset_snapshot_status(self):
+ # Reset snapshot status to creating
+ status = 'creating'
+ resp, body = self.admin_snapshots_client.\
+ reset_snapshot_status(self.snapshot['id'], status)
+ self.assertEqual(202, resp.status)
+ resp_get, snapshot_get \
+ = self.admin_snapshots_client.get_snapshot(self.snapshot['id'])
+ self.assertEqual(200, resp_get.status)
+ self.assertEqual(status, snapshot_get['status'])
+
+ @attr(type='gate')
+ def test_update_snapshot_status(self):
+ # Reset snapshot status to creating
+ status = 'creating'
+ self.admin_snapshots_client.\
+ reset_snapshot_status(self.snapshot['id'], status)
+
+ # Update snapshot status to error
+ progress = '80%'
+ status = 'error'
+ progress_alias = self._get_progress_alias()
+ resp, body = self.client.update_snapshot_status(self.snapshot['id'],
+ status, progress)
+ self.assertEqual(202, resp.status)
+ resp_get, snapshot_get \
+ = self.admin_snapshots_client.get_snapshot(self.snapshot['id'])
+ self.assertEqual(200, resp_get.status)
+ self.assertEqual(status, snapshot_get['status'])
+ self.assertEqual(progress, snapshot_get[progress_alias])
+
+
+class SnapshotsActionsTestXML(SnapshotsActionsTest):
+ _interface = "xml"
+
+ def _get_progress_alias(self):
+ return '{http://docs.openstack.org/volume/ext' \
+ '/extended_snapshot_attributes/api/v1}progress'
diff --git a/tempest/api/volume/admin/test_volume_types.py b/tempest/api/volume/admin/test_volume_types.py
index c455566..5218f83 100644
--- a/tempest/api/volume/admin/test_volume_types.py
+++ b/tempest/api/volume/admin/test_volume_types.py
@@ -16,7 +16,7 @@
# under the License.
from tempest.api.volume.base import BaseVolumeTest
-from tempest.common.utils.data_utils import rand_name
+from tempest.common.utils import data_utils
from tempest.services.volume.json.admin import volume_types_client
from tempest.test import attr
@@ -58,8 +58,8 @@
def test_create_get_delete_volume_with_volume_type_and_extra_specs(self):
# Create/get/delete volume with volume_type and extra spec.
volume = {}
- vol_name = rand_name("volume-")
- vol_type_name = rand_name("volume-type-")
+ vol_name = data_utils.rand_name("volume-")
+ vol_type_name = data_utils.rand_name("volume-type-")
proto = self.config.volume.storage_protocol
vendor = self.config.volume.vendor_name
extra_specs = {"storage_protocol": proto,
@@ -99,31 +99,14 @@
'from the created Volume')
@attr(type='smoke')
- def test_volume_type_create_delete(self):
- # Create/Delete volume type.
- name = rand_name("volume-type-")
- extra_specs = {"storage_protocol": "iSCSI",
- "vendor_name": "Open Source"}
- resp, body = self.client.create_volume_type(
- name,
- extra_specs=extra_specs)
- self.assertEqual(200, resp.status)
- self.assertIn('id', body)
- self.addCleanup(self._delete_volume_type, body['id'])
- self.assertIn('name', body)
- self.assertEqual(body['name'], name,
- "The created volume_type name is not equal "
- "to the requested name")
- self.assertTrue(body['id'] is not None,
- "Field volume_type id is empty or not found.")
-
- @attr(type='smoke')
- def test_volume_type_create_get(self):
+ def test_volume_type_create_get_delete(self):
# Create/get volume type.
body = {}
- name = rand_name("volume-type-")
- extra_specs = {"storage_protocol": "iSCSI",
- "vendor_name": "Open Source"}
+ name = data_utils.rand_name("volume-type-")
+ proto = self.config.volume.storage_protocol
+ vendor = self.config.volume.vendor_name
+ extra_specs = {"storage_protocol": proto,
+ "vendor_name": vendor}
resp, body = self.client.create_volume_type(
name,
extra_specs=extra_specs)
diff --git a/tempest/api/volume/admin/test_volume_types_extra_specs.py b/tempest/api/volume/admin/test_volume_types_extra_specs.py
index d6dd7db..dbb75af 100644
--- a/tempest/api/volume/admin/test_volume_types_extra_specs.py
+++ b/tempest/api/volume/admin/test_volume_types_extra_specs.py
@@ -16,7 +16,7 @@
# under the License.
from tempest.api.volume import base
-from tempest.common.utils.data_utils import rand_name
+from tempest.common.utils import data_utils
from tempest.test import attr
@@ -26,7 +26,7 @@
@classmethod
def setUpClass(cls):
super(VolumeTypesExtraSpecsTest, cls).setUpClass()
- vol_type_name = rand_name('Volume-type-')
+ vol_type_name = data_utils.rand_name('Volume-type-')
resp, cls.volume_type = cls.client.create_volume_type(vol_type_name)
@classmethod
@@ -47,8 +47,7 @@
self.volume_type['id'])
self.assertEqual(200, resp.status)
self.assertIsInstance(body, dict)
- self.assertTrue('spec1' in body, "Incorrect volume type extra"
- " spec returned")
+ self.assertIn('spec1', body)
@attr(type='gate')
def test_volume_type_extra_specs_update(self):
@@ -66,8 +65,7 @@
extra_spec.keys()[0],
extra_spec)
self.assertEqual(200, resp.status)
- self.assertTrue('spec2' in body,
- "Volume type extra spec incorrectly updated")
+ self.assertIn('spec2', body)
self.assertEqual(extra_spec['spec2'], body['spec2'],
"Volume type extra spec incorrectly updated")
diff --git a/tempest/api/volume/admin/test_volume_types_extra_specs_negative.py b/tempest/api/volume/admin/test_volume_types_extra_specs_negative.py
index e76c0ac..8b5dce2 100644
--- a/tempest/api/volume/admin/test_volume_types_extra_specs_negative.py
+++ b/tempest/api/volume/admin/test_volume_types_extra_specs_negative.py
@@ -18,7 +18,7 @@
import uuid
from tempest.api.volume import base
-from tempest.common.utils.data_utils import rand_name
+from tempest.common.utils import data_utils
from tempest import exceptions
from tempest.test import attr
@@ -29,7 +29,7 @@
@classmethod
def setUpClass(cls):
super(ExtraSpecsNegativeTest, cls).setUpClass()
- vol_type_name = rand_name('Volume-type-')
+ vol_type_name = data_utils.rand_name('Volume-type-')
cls.extra_specs = {"spec1": "val1"}
resp, cls.volume_type = cls.client.create_volume_type(vol_type_name,
extra_specs=
diff --git a/tempest/api/volume/admin/test_volumes_actions.py b/tempest/api/volume/admin/test_volumes_actions.py
new file mode 100644
index 0000000..4063eef
--- /dev/null
+++ b/tempest/api/volume/admin/test_volumes_actions.py
@@ -0,0 +1,89 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 Huawei Technologies Co.,LTD
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.api.volume.base import BaseVolumeAdminTest
+from tempest.common.utils import data_utils as utils
+from tempest.test import attr
+
+
+class VolumesActionsTest(BaseVolumeAdminTest):
+ _interface = "json"
+
+ @classmethod
+ def setUpClass(cls):
+ super(VolumesActionsTest, cls).setUpClass()
+ cls.client = cls.volumes_client
+
+ # Create admin volume client
+ cls.admin_volume_client = cls.os_adm.volumes_client
+
+ # Create a test shared volume for tests
+ vol_name = utils.rand_name(cls.__name__ + '-Volume-')
+
+ resp, cls.volume = cls.client.create_volume(size=1,
+ display_name=vol_name)
+ cls.client.wait_for_volume_status(cls.volume['id'], 'available')
+
+ @classmethod
+ def tearDownClass(cls):
+ # Delete the test volume
+ cls.client.delete_volume(cls.volume['id'])
+ cls.client.wait_for_resource_deletion(cls.volume['id'])
+
+ super(VolumesActionsTest, cls).tearDownClass()
+
+ def _reset_volume_status(self, volume_id, status):
+ #Reset the volume status
+ resp, body = self.admin_volume_client.reset_volume_status(volume_id,
+ status)
+ return resp, body
+
+ def tearDown(self):
+ # Set volume's status to available after test
+ self._reset_volume_status(self.volume['id'], 'available')
+ super(VolumesActionsTest, self).tearDown()
+
+ @attr(type='gate')
+ def test_volume_reset_status(self):
+ # test volume reset status : available->error->available
+ resp, body = self._reset_volume_status(self.volume['id'], 'error')
+ self.assertEqual(202, resp.status)
+ resp_get, volume_get = self.admin_volume_client.get_volume(
+ self.volume['id'])
+ self.assertEqual('error', volume_get['status'])
+
+ @attr(type='gate')
+ def test_volume_begin_detaching(self):
+ # test volume begin detaching : available -> detaching -> available
+ resp, body = self.client.volume_begin_detaching(self.volume['id'])
+ self.assertEqual(202, resp.status)
+ resp_get, volume_get = self.client.get_volume(self.volume['id'])
+ self.assertEqual('detaching', volume_get['status'])
+
+ @attr(type='gate')
+ def test_volume_roll_detaching(self):
+ # test volume roll detaching : detaching -> in-use -> available
+ resp, body = self.client.volume_begin_detaching(self.volume['id'])
+ self.assertEqual(202, resp.status)
+ resp, body = self.client.volume_roll_detaching(self.volume['id'])
+ self.assertEqual(202, resp.status)
+ resp_get, volume_get = self.client.get_volume(self.volume['id'])
+ self.assertEqual('in-use', volume_get['status'])
+
+
+class VolumesActionsTestXML(VolumesActionsTest):
+ _interface = "xml"
diff --git a/tempest/api/volume/base.py b/tempest/api/volume/base.py
index 98694c5..cdf8638 100644
--- a/tempest/api/volume/base.py
+++ b/tempest/api/volume/base.py
@@ -18,7 +18,6 @@
import time
from tempest import clients
-from tempest.common import isolated_creds
from tempest.openstack.common import log as logging
import tempest.test
@@ -32,21 +31,12 @@
@classmethod
def setUpClass(cls):
super(BaseVolumeTest, cls).setUpClass()
- cls.isolated_creds = isolated_creds.IsolatedCreds(cls.__name__)
if not cls.config.service_available.cinder:
skip_msg = ("%s skipped as Cinder is not available" % cls.__name__)
raise cls.skipException(skip_msg)
- if cls.config.compute.allow_tenant_isolation:
- creds = cls.isolated_creds.get_primary_creds()
- username, tenant_name, password = creds
- os = clients.Manager(username=username,
- password=password,
- tenant_name=tenant_name,
- interface=cls._interface)
- else:
- os = clients.Manager(interface=cls._interface)
+ os = cls.get_client_manager()
cls.os = os
cls.volumes_client = os.volumes_client
@@ -69,7 +59,7 @@
def tearDownClass(cls):
cls.clear_snapshots()
cls.clear_volumes()
- cls.isolated_creds.clear_isolated_creds()
+ cls.clear_isolated_creds()
super(BaseVolumeTest, cls).tearDownClass()
@classmethod
diff --git a/tempest/api/volume/test_volume_transfers.py b/tempest/api/volume/test_volume_transfers.py
new file mode 100644
index 0000000..dacebf1
--- /dev/null
+++ b/tempest/api/volume/test_volume_transfers.py
@@ -0,0 +1,120 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 OpenStack Foundation
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.api.volume.base import BaseVolumeTest
+from tempest import clients
+from tempest.common.utils.data_utils import rand_name
+from tempest.test import attr
+
+
+class VolumesTransfersTest(BaseVolumeTest):
+ _interface = "json"
+
+ @classmethod
+ def setUpClass(cls):
+ super(VolumesTransfersTest, cls).setUpClass()
+
+ # Add another tenant to test volume-transfer
+ if cls.config.compute.allow_tenant_isolation:
+ creds = cls.isolated_creds.get_alt_creds()
+ username, tenant_name, password = creds
+ cls.os_alt = clients.Manager(username=username,
+ password=password,
+ tenant_name=tenant_name,
+ interface=cls._interface)
+ cls.alt_tenant_id = cls.isolated_creds.get_alt_tenant()['id']
+
+ # Add admin tenant to cleanup resources
+ adm_creds = cls.isolated_creds.get_admin_creds()
+ admin_username, admin_tenant_name, admin_password = adm_creds
+ cls.os_adm = clients.Manager(username=admin_username,
+ password=admin_password,
+ tenant_name=admin_tenant_name,
+ interface=cls._interface)
+ else:
+ cls.os_alt = clients.AltManager()
+ alt_tenant_name = cls.os_alt.tenant_name
+ identity_client = cls._get_identity_admin_client()
+ _, tenants = identity_client.list_tenants()
+ cls.alt_tenant_id = [tnt['id'] for tnt in tenants
+ if tnt['name'] == alt_tenant_name][0]
+ cls.os_adm = clients.ComputeAdminManager(interface=cls._interface)
+
+ cls.client = cls.volumes_client
+ cls.alt_client = cls.os_alt.volumes_client
+ cls.adm_client = cls.os_adm.volumes_client
+
+ @attr(type='gate')
+ def test_create_get_list_accept_volume_transfer(self):
+ # Create a volume first
+ vol_name = rand_name('-Volume-')
+ _, volume = self.client.create_volume(size=1, display_name=vol_name)
+ self.addCleanup(self.adm_client.delete_volume, volume['id'])
+ self.client.wait_for_volume_status(volume['id'], 'available')
+
+ # Create a volume transfer
+ resp, transfer = self.client.create_volume_transfer(volume['id'])
+ self.assertEqual(202, resp.status)
+ transfer_id = transfer['id']
+ auth_key = transfer['auth_key']
+ self.client.wait_for_volume_status(volume['id'],
+ 'awaiting-transfer')
+
+ # Get a volume transfer
+ resp, body = self.client.get_volume_transfer(transfer_id)
+ self.assertEqual(200, resp.status)
+ self.assertEqual(volume['id'], body['volume_id'])
+
+ # List volume transfers, the result should be greater than
+ # or equal to 1
+ resp, body = self.client.list_volume_transfers()
+ self.assertEqual(200, resp.status)
+ self.assertGreaterEqual(len(body), 1)
+
+ # Accept a volume transfer by alt_tenant
+ resp, body = self.alt_client.accept_volume_transfer(transfer_id,
+ auth_key)
+ self.assertEqual(202, resp.status)
+ self.alt_client.wait_for_volume_status(volume['id'], 'available')
+
+ def test_create_list_delete_volume_transfer(self):
+ # Create a volume first
+ vol_name = rand_name('-Volume-')
+ _, volume = self.client.create_volume(size=1, display_name=vol_name)
+ self.addCleanup(self.adm_client.delete_volume, volume['id'])
+ self.client.wait_for_volume_status(volume['id'], 'available')
+
+ # Create a volume transfer
+ resp, body = self.client.create_volume_transfer(volume['id'])
+ self.assertEqual(202, resp.status)
+ transfer_id = body['id']
+ self.client.wait_for_volume_status(volume['id'],
+ 'awaiting-transfer')
+
+ # List all volume transfers, there's only one in this test
+ resp, body = self.client.list_volume_transfers()
+ self.assertEqual(200, resp.status)
+ self.assertEqual(volume['id'], body[0]['volume_id'])
+
+ # Delete a volume transfer
+ resp, body = self.client.delete_volume_transfer(transfer_id)
+ self.assertEqual(202, resp.status)
+ self.client.wait_for_volume_status(volume['id'], 'available')
+
+
+class VolumesTransfersTestXML(VolumesTransfersTest):
+ _interface = "xml"
diff --git a/tempest/api/volume/test_volumes_actions.py b/tempest/api/volume/test_volumes_actions.py
index 09131e2..8581d16 100644
--- a/tempest/api/volume/test_volumes_actions.py
+++ b/tempest/api/volume/test_volumes_actions.py
@@ -16,7 +16,7 @@
# under the License.
from tempest.api.volume.base import BaseVolumeTest
-from tempest.common.utils.data_utils import rand_name
+from tempest.common.utils import data_utils
from tempest.test import attr
from tempest.test import services
from tempest.test import stresstest
@@ -32,8 +32,8 @@
cls.image_client = cls.os.image_client
# Create a test shared instance and volume for attach/detach tests
- srv_name = rand_name(cls.__name__ + '-Instance-')
- vol_name = rand_name(cls.__name__ + '-Volume-')
+ srv_name = data_utils.rand_name(cls.__name__ + '-Instance-')
+ vol_name = data_utils.rand_name(cls.__name__ + '-Volume-')
resp, cls.server = cls.servers_client.create_server(srv_name,
cls.image_ref,
cls.flavor_ref)
@@ -102,7 +102,7 @@
# it is shared with the other tests. After it is uploaded in Glance,
# there is no way to delete it from Cinder, so we delete it from Glance
# using the Glance image_client and from Cinder via tearDownClass.
- image_name = rand_name('Image-')
+ image_name = data_utils.rand_name('Image-')
resp, body = self.client.upload_volume(self.volume['id'],
image_name,
self.config.volume.disk_format)
@@ -112,6 +112,63 @@
self.image_client.wait_for_image_status(image_id, 'active')
self.client.wait_for_volume_status(self.volume['id'], 'available')
+ @attr(type='gate')
+ def test_volume_extend(self):
+ # Extend Volume Test.
+ extend_size = int(self.volume['size']) + 1
+ resp, body = self.client.extend_volume(self.volume['id'], extend_size)
+ self.assertEqual(202, resp.status)
+ self.client.wait_for_volume_status(self.volume['id'], 'available')
+ resp, volume = self.client.get_volume(self.volume['id'])
+ self.assertEqual(200, resp.status)
+ self.assertEqual(int(volume['size']), extend_size)
+
+ @attr(type='gate')
+ def test_reserve_unreserve_volume(self):
+ # Mark volume as reserved.
+ resp, body = self.client.reserve_volume(self.volume['id'])
+ self.assertEqual(202, resp.status)
+ # To get the volume info
+ resp, body = self.client.get_volume(self.volume['id'])
+ self.assertEqual(200, resp.status)
+ self.assertIn('attaching', body['status'])
+ # Unmark volume as reserved.
+ resp, body = self.client.unreserve_volume(self.volume['id'])
+ self.assertEqual(202, resp.status)
+ # To get the volume info
+ resp, body = self.client.get_volume(self.volume['id'])
+ self.assertEqual(200, resp.status)
+ self.assertIn('available', body['status'])
+
+ def _is_true(self, val):
+ return val in ['true', 'True', True]
+
+ @attr(type='gate')
+ def test_volume_readonly_update(self):
+ # Update volume readonly true
+ readonly = True
+ resp, body = self.client.update_volume_readonly(self.volume['id'],
+ readonly)
+ self.assertEqual(202, resp.status)
+
+ # Get Volume information
+ resp, fetched_volume = self.client.get_volume(self.volume['id'])
+ bool_flag = self._is_true(fetched_volume['metadata']['readonly'])
+ self.assertEqual(200, resp.status)
+ self.assertEqual(True, bool_flag)
+
+ # Update volume readonly false
+ readonly = False
+ resp, body = self.client.update_volume_readonly(self.volume['id'],
+ readonly)
+ self.assertEqual(202, resp.status)
+
+ # Get Volume information
+ resp, fetched_volume = self.client.get_volume(self.volume['id'])
+ bool_flag = self._is_true(fetched_volume['metadata']['readonly'])
+ self.assertEqual(200, resp.status)
+ self.assertEqual(False, bool_flag)
+
class VolumesActionsTestXML(VolumesActionsTest):
_interface = "xml"
diff --git a/tempest/api/volume/test_volumes_get.py b/tempest/api/volume/test_volumes_get.py
index f2915f7..14120fe 100644
--- a/tempest/api/volume/test_volumes_get.py
+++ b/tempest/api/volume/test_volumes_get.py
@@ -16,7 +16,7 @@
# under the License.
from tempest.api.volume import base
-from tempest.common.utils.data_utils import rand_name
+from tempest.common.utils import data_utils
from tempest.test import attr
from tempest.test import services
@@ -46,7 +46,7 @@
def _volume_create_get_update_delete(self, **kwargs):
# Create a volume, Get it's details and Delete the volume
volume = {}
- v_name = rand_name('Volume')
+ v_name = data_utils.rand_name('Volume')
metadata = {'Type': 'Test'}
# Create a volume
resp, volume = self.client.create_volume(size=1,
@@ -88,7 +88,7 @@
self.assertEqual(boot_flag, False)
# Update Volume
- new_v_name = rand_name('new-Volume')
+ new_v_name = data_utils.rand_name('new-Volume')
new_desc = 'This is the new description of volume'
resp, update_volume = \
self.client.update_volume(volume['id'],
@@ -118,7 +118,7 @@
def test_volume_get_metadata_none(self):
# Create a volume without passing metadata, get details, and delete
volume = {}
- v_name = rand_name('Volume-')
+ v_name = data_utils.rand_name('Volume-')
# Create a volume without metadata
resp, volume = self.client.create_volume(size=1,
display_name=v_name,
diff --git a/tempest/api/volume/test_volumes_list.py b/tempest/api/volume/test_volumes_list.py
index 2aaa71d..4dbc88a 100644
--- a/tempest/api/volume/test_volumes_list.py
+++ b/tempest/api/volume/test_volumes_list.py
@@ -17,8 +17,7 @@
# under the License.
from tempest.api.volume import base
-from tempest.common.utils.data_utils import rand_int_id
-from tempest.common.utils.data_utils import rand_name
+from tempest.common.utils import data_utils
from tempest.openstack.common import log as logging
from tempest.test import attr
@@ -59,7 +58,7 @@
cls.volume_list = []
cls.volume_id_list = []
for i in range(3):
- v_name = rand_name('volume')
+ v_name = data_utils.rand_name('volume')
metadata = {'Type': 'work'}
try:
resp, volume = cls.client.create_volume(size=1,
@@ -107,7 +106,7 @@
@attr(type='gate')
def test_volume_list_by_name(self):
- volume = self.volume_list[rand_int_id(0, 2)]
+ volume = self.volume_list[data_utils.rand_int_id(0, 2)]
params = {'display_name': volume['display_name']}
resp, fetched_vol = self.client.list_volumes(params)
self.assertEqual(200, resp.status)
@@ -117,7 +116,7 @@
@attr(type='gate')
def test_volume_list_details_by_name(self):
- volume = self.volume_list[rand_int_id(0, 2)]
+ volume = self.volume_list[data_utils.rand_int_id(0, 2)]
params = {'display_name': volume['display_name']}
resp, fetched_vol = self.client.list_volumes_with_detail(params)
self.assertEqual(200, resp.status)
@@ -145,7 +144,7 @@
@attr(type='gate')
def test_volumes_list_by_availability_zone(self):
- volume = self.volume_list[rand_int_id(0, 2)]
+ volume = self.volume_list[data_utils.rand_int_id(0, 2)]
zone = volume['availability_zone']
params = {'availability_zone': zone}
resp, fetched_list = self.client.list_volumes(params)
@@ -156,7 +155,7 @@
@attr(type='gate')
def test_volumes_list_details_by_availability_zone(self):
- volume = self.volume_list[rand_int_id(0, 2)]
+ volume = self.volume_list[data_utils.rand_int_id(0, 2)]
zone = volume['availability_zone']
params = {'availability_zone': zone}
resp, fetched_list = self.client.list_volumes_with_detail(params)
diff --git a/tempest/api/volume/test_volumes_negative.py b/tempest/api/volume/test_volumes_negative.py
index 3e2b6ad..928bd49 100644
--- a/tempest/api/volume/test_volumes_negative.py
+++ b/tempest/api/volume/test_volumes_negative.py
@@ -18,7 +18,7 @@
import uuid
from tempest.api.volume import base
-from tempest.common.utils.data_utils import rand_name
+from tempest.common.utils import data_utils
from tempest import exceptions
from tempest.test import attr
@@ -32,7 +32,7 @@
cls.client = cls.volumes_client
# Create a test shared instance and volume for attach/detach tests
- vol_name = rand_name('Volume-')
+ vol_name = data_utils.rand_name('Volume-')
cls.volume = cls.create_volume(size=1, display_name=vol_name)
cls.client.wait_for_volume_status(cls.volume['id'], 'available')
@@ -54,7 +54,7 @@
def test_create_volume_with_invalid_size(self):
# Should not be able to create volume with invalid size
# in request
- v_name = rand_name('Volume-')
+ v_name = data_utils.rand_name('Volume-')
metadata = {'Type': 'work'}
self.assertRaises(exceptions.BadRequest, self.client.create_volume,
size='#$%', display_name=v_name, metadata=metadata)
@@ -63,7 +63,7 @@
def test_create_volume_with_out_passing_size(self):
# Should not be able to create volume without passing size
# in request
- v_name = rand_name('Volume-')
+ v_name = data_utils.rand_name('Volume-')
metadata = {'Type': 'work'}
self.assertRaises(exceptions.BadRequest, self.client.create_volume,
size='', display_name=v_name, metadata=metadata)
@@ -71,7 +71,7 @@
@attr(type=['negative', 'gate'])
def test_create_volume_with_size_zero(self):
# Should not be able to create volume with size zero
- v_name = rand_name('Volume-')
+ v_name = data_utils.rand_name('Volume-')
metadata = {'Type': 'work'}
self.assertRaises(exceptions.BadRequest, self.client.create_volume,
size='0', display_name=v_name, metadata=metadata)
@@ -79,14 +79,41 @@
@attr(type=['negative', 'gate'])
def test_create_volume_with_size_negative(self):
# Should not be able to create volume with size negative
- v_name = rand_name('Volume-')
+ v_name = data_utils.rand_name('Volume-')
metadata = {'Type': 'work'}
self.assertRaises(exceptions.BadRequest, self.client.create_volume,
size='-1', display_name=v_name, metadata=metadata)
@attr(type=['negative', 'gate'])
+ def test_create_volume_with_nonexistant_volume_type(self):
+ # Should not be able to create volume with non-existant volume type
+ v_name = data_utils.rand_name('Volume-')
+ metadata = {'Type': 'work'}
+ self.assertRaises(exceptions.NotFound, self.client.create_volume,
+ size='1', volume_type=str(uuid.uuid4()),
+ display_name=v_name, metadata=metadata)
+
+ @attr(type=['negative', 'gate'])
+ def test_create_volume_with_nonexistant_snapshot_id(self):
+ # Should not be able to create volume with non-existant snapshot
+ v_name = data_utils.rand_name('Volume-')
+ metadata = {'Type': 'work'}
+ self.assertRaises(exceptions.NotFound, self.client.create_volume,
+ size='1', snapshot_id=str(uuid.uuid4()),
+ display_name=v_name, metadata=metadata)
+
+ @attr(type=['negative', 'gate'])
+ def test_create_volume_with_nonexistant_source_volid(self):
+ # Should not be able to create volume with non-existant source volume
+ v_name = data_utils.rand_name('Volume-')
+ metadata = {'Type': 'work'}
+ self.assertRaises(exceptions.NotFound, self.client.create_volume,
+ size='1', source_volid=str(uuid.uuid4()),
+ display_name=v_name, metadata=metadata)
+
+ @attr(type=['negative', 'gate'])
def test_update_volume_with_nonexistant_volume_id(self):
- v_name = rand_name('Volume-')
+ v_name = data_utils.rand_name('Volume-')
metadata = {'Type': 'work'}
self.assertRaises(exceptions.NotFound, self.client.update_volume,
volume_id=str(uuid.uuid4()), display_name=v_name,
@@ -94,7 +121,7 @@
@attr(type=['negative', 'gate'])
def test_update_volume_with_invalid_volume_id(self):
- v_name = rand_name('Volume-')
+ v_name = data_utils.rand_name('Volume-')
metadata = {'Type': 'work'}
self.assertRaises(exceptions.NotFound, self.client.update_volume,
volume_id='#$%%&^&^', display_name=v_name,
@@ -102,7 +129,7 @@
@attr(type=['negative', 'gate'])
def test_update_volume_with_empty_volume_id(self):
- v_name = rand_name('Volume-')
+ v_name = data_utils.rand_name('Volume-')
metadata = {'Type': 'work'}
self.assertRaises(exceptions.NotFound, self.client.update_volume,
volume_id='', display_name=v_name,
@@ -132,7 +159,7 @@
@attr(type=['negative', 'gate'])
def test_attach_volumes_with_nonexistent_volume_id(self):
- srv_name = rand_name('Instance-')
+ srv_name = data_utils.rand_name('Instance-')
resp, server = self.servers_client.create_server(srv_name,
self.image_ref,
self.flavor_ref)
@@ -150,6 +177,96 @@
self.client.detach_volume,
'xxx')
+ @attr(type=['negative', 'gate'])
+ def test_volume_extend_with_size_smaller_than_original_size(self):
+ # Extend volume with smaller size than original size.
+ extend_size = 0
+ self.assertRaises(exceptions.BadRequest, self.client.extend_volume,
+ self.volume['id'], extend_size)
+
+ @attr(type=['negative', 'gate'])
+ def test_volume_extend_with_non_number_size(self):
+ # Extend volume when size is non number.
+ extend_size = 'abc'
+ self.assertRaises(exceptions.BadRequest, self.client.extend_volume,
+ self.volume['id'], extend_size)
+
+ @attr(type=['negative', 'gate'])
+ def test_volume_extend_with_None_size(self):
+ # Extend volume with None size.
+ extend_size = None
+ self.assertRaises(exceptions.BadRequest, self.client.extend_volume,
+ self.volume['id'], extend_size)
+
+ @attr(type=['negative', 'gate'])
+ def test_volume_extend_with_nonexistent_volume_id(self):
+ # Extend volume size when volume is nonexistent.
+ extend_size = int(self.volume['size']) + 1
+ self.assertRaises(exceptions.NotFound, self.client.extend_volume,
+ str(uuid.uuid4()), extend_size)
+
+ @attr(type=['negative', 'gate'])
+ def test_volume_extend_without_passing_volume_id(self):
+ # Extend volume size when passing volume id is None.
+ extend_size = int(self.volume['size']) + 1
+ self.assertRaises(exceptions.NotFound, self.client.extend_volume,
+ None, extend_size)
+
+ @attr(type=['negative', 'gate'])
+ def test_reserve_volume_with_nonexistent_volume_id(self):
+ self.assertRaises(exceptions.NotFound,
+ self.client.reserve_volume,
+ str(uuid.uuid4()))
+
+ @attr(type=['negative', 'gate'])
+ def test_unreserve_volume_with_nonexistent_volume_id(self):
+ self.assertRaises(exceptions.NotFound,
+ self.client.unreserve_volume,
+ str(uuid.uuid4()))
+
+ @attr(type=['negative', 'gate'])
+ def test_reserve_volume_with_negative_volume_status(self):
+ # Mark volume as reserved.
+ resp, body = self.client.reserve_volume(self.volume['id'])
+ self.assertEqual(202, resp.status)
+ # Mark volume which is marked as reserved before
+ self.assertRaises(exceptions.BadRequest,
+ self.client.reserve_volume,
+ self.volume['id'])
+ # Unmark volume as reserved.
+ resp, body = self.client.unreserve_volume(self.volume['id'])
+ self.assertEqual(202, resp.status)
+
+ @attr(type=['negative', 'gate'])
+ def test_list_volumes_with_nonexistent_name(self):
+ v_name = data_utils.rand_name('Volume-')
+ params = {'display_name': v_name}
+ resp, fetched_volume = self.client.list_volumes(params)
+ self.assertEqual(200, resp.status)
+ self.assertEqual(0, len(fetched_volume))
+
+ @attr(type=['negative', 'gate'])
+ def test_list_volumes_detail_with_nonexistent_name(self):
+ v_name = data_utils.rand_name('Volume-')
+ params = {'display_name': v_name}
+ resp, fetched_volume = self.client.list_volumes_with_detail(params)
+ self.assertEqual(200, resp.status)
+ self.assertEqual(0, len(fetched_volume))
+
+ @attr(type=['negative', 'gate'])
+ def test_list_volumes_with_invalid_status(self):
+ params = {'status': 'null'}
+ resp, fetched_volume = self.client.list_volumes(params)
+ self.assertEqual(200, resp.status)
+ self.assertEqual(0, len(fetched_volume))
+
+ @attr(type=['negative', 'gate'])
+ def test_list_volumes_detail_with_invalid_status(self):
+ params = {'status': 'null'}
+ resp, fetched_volume = self.client.list_volumes_with_detail(params)
+ self.assertEqual(200, resp.status)
+ self.assertEqual(0, len(fetched_volume))
+
class VolumesNegativeTestXML(VolumesNegativeTest):
_interface = 'xml'
diff --git a/tempest/api/volume/test_volumes_snapshots.py b/tempest/api/volume/test_volumes_snapshots.py
index 6b186e5..99e8de7 100644
--- a/tempest/api/volume/test_volumes_snapshots.py
+++ b/tempest/api/volume/test_volumes_snapshots.py
@@ -13,7 +13,7 @@
# under the License.
from tempest.api.volume import base
-from tempest.common.utils.data_utils import rand_name
+from tempest.common.utils import data_utils
from tempest.openstack.common import log as logging
from tempest.test import attr
@@ -40,7 +40,7 @@
@attr(type='gate')
def test_snapshot_create_get_list_update_delete(self):
# Create a snapshot
- s_name = rand_name('snap')
+ s_name = data_utils.rand_name('snap')
snapshot = self.create_snapshot(self.volume_origin['id'],
display_name=s_name)
@@ -59,7 +59,7 @@
self.assertIn(tracking_data, snaps_data)
# Updates snapshot with new values
- new_s_name = rand_name('new-snap')
+ new_s_name = data_utils.rand_name('new-snap')
new_desc = 'This is the new description of snapshot.'
resp, update_snapshot = \
self.snapshots_client.update_snapshot(snapshot['id'],
diff --git a/tempest/api/volume/test_volumes_snapshots_negative.py b/tempest/api/volume/test_volumes_snapshots_negative.py
new file mode 100644
index 0000000..04a4774
--- /dev/null
+++ b/tempest/api/volume/test_volumes_snapshots_negative.py
@@ -0,0 +1,44 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# 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 uuid
+
+from tempest.api.volume import base
+from tempest.common.utils import data_utils
+from tempest import exceptions
+from tempest.test import attr
+
+
+class VolumesSnapshotNegativeTest(base.BaseVolumeTest):
+ _interface = "json"
+
+ @attr(type=['negative', 'gate'])
+ def test_create_snapshot_with_nonexistent_volume_id(self):
+ # Create a snapshot with nonexistent volume id
+ s_name = data_utils.rand_name('snap')
+ self.assertRaises(exceptions.NotFound,
+ self.snapshots_client.create_snapshot,
+ str(uuid.uuid4()), display_name=s_name)
+
+ @attr(type=['negative', 'gate'])
+ def test_create_snapshot_without_passing_volume_id(self):
+ # Create a snapshot without passing volume id
+ s_name = data_utils.rand_name('snap')
+ self.assertRaises(exceptions.NotFound,
+ self.snapshots_client.create_snapshot,
+ None, display_name=s_name)
+
+
+class VolumesSnapshotNegativeTestXML(VolumesSnapshotNegativeTest):
+ _interface = "xml"
diff --git a/tempest/cli/__init__.py b/tempest/cli/__init__.py
index b082b1e..ec8b3a1 100644
--- a/tempest/cli/__init__.py
+++ b/tempest/cli/__init__.py
@@ -33,7 +33,7 @@
default=True,
help="enable cli tests"),
cfg.StrOpt('cli_dir',
- default='/usr/local/bin/',
+ default='/usr/local/bin',
help="directory where python client binaries are located"),
cfg.IntOpt('timeout',
default=15,
@@ -80,6 +80,12 @@
return self.cmd_with_auth(
'glance', action, flags, params, admin, fail_ok)
+ def ceilometer(self, action, flags='', params='', admin=True,
+ fail_ok=False):
+ """Executes ceilometer command for the given action."""
+ return self.cmd_with_auth(
+ 'ceilometer', action, flags, params, admin, fail_ok)
+
def cinder(self, action, flags='', params='', admin=True, fail_ok=False):
"""Executes cinder command for the given action."""
return self.cmd_with_auth(
@@ -122,7 +128,8 @@
if not fail_ok and proc.returncode != 0:
raise CommandFailed(proc.returncode,
cmd,
- result)
+ result,
+ stderr=result_err)
finally:
LOG.debug('output of %s:\n%s' % (cmd_str, result))
if not merge_stderr and result_err:
@@ -143,6 +150,7 @@
class CommandFailed(subprocess.CalledProcessError):
# adds output attribute for python2.6
- def __init__(self, returncode, cmd, output):
+ def __init__(self, returncode, cmd, output, stderr=""):
super(CommandFailed, self).__init__(returncode, cmd)
self.output = output
+ self.stderr = stderr
diff --git a/tempest/cli/simple_read_only/test_ceilometer.py b/tempest/cli/simple_read_only/test_ceilometer.py
new file mode 100644
index 0000000..7f2864f
--- /dev/null
+++ b/tempest/cli/simple_read_only/test_ceilometer.py
@@ -0,0 +1,51 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 OpenStack Foundation
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from oslo.config import cfg
+
+import tempest.cli
+from tempest.openstack.common import log as logging
+
+CONF = cfg.CONF
+
+LOG = logging.getLogger(__name__)
+
+
+class SimpleReadOnlyCeilometerClientTest(tempest.cli.ClientTestBase):
+ """Basic, read-only tests for Ceilometer CLI client.
+
+ Checks return values and output of read-only commands.
+ These tests do not presume any content, nor do they create
+ their own. They only verify the structure of output if present.
+ """
+
+ @classmethod
+ def setUpClass(cls):
+ if (not CONF.service_available.ceilometer):
+ msg = ("Skiping all Ceilometer cli tests because it is"
+ "not available")
+ raise cls.skipException(msg)
+ super(SimpleReadOnlyCeilometerClientTest, cls).setUpClass()
+
+ def test_ceilometer_meter_list(self):
+ self.ceilometer('meter-list')
+
+ def test_ceilometer_resource_list(self):
+ self.ceilometer('resource-list')
+
+ def test_ceilometermeter_alarm_list(self):
+ self.ceilometer('alarm-list')
diff --git a/tempest/cli/simple_read_only/test_cinder.py b/tempest/cli/simple_read_only/test_cinder.py
index 719ba3c..eb5df63 100644
--- a/tempest/cli/simple_read_only/test_cinder.py
+++ b/tempest/cli/simple_read_only/test_cinder.py
@@ -84,6 +84,24 @@
roles = self.parser.listing(self.cinder('list-extensions'))
self.assertTableStruct(roles, ['Name', 'Summary', 'Alias', 'Updated'])
+ def test_cinder_credentials(self):
+ self.cinder('credentials')
+
+ def test_cinder_availability_zone_list(self):
+ self.cinder('availability-zone-list')
+
+ def test_cinder_endpoints(self):
+ self.cinder('endpoints')
+
+ def test_cinder_service_list(self):
+ self.cinder('service-list')
+
+ def test_cinder_transfer_list(self):
+ self.cinder('transfer-list')
+
+ def test_cinder_bash_completion(self):
+ self.cinder('bash-completion')
+
def test_admin_help(self):
help_text = self.cinder('help')
lines = help_text.split('\n')
diff --git a/tempest/cli/simple_read_only/test_glance.py b/tempest/cli/simple_read_only/test_glance.py
index d02c60b..a5a229c 100644
--- a/tempest/cli/simple_read_only/test_glance.py
+++ b/tempest/cli/simple_read_only/test_glance.py
@@ -18,9 +18,13 @@
import re
import subprocess
+from oslo.config import cfg
+
import tempest.cli
from tempest.openstack.common import log as logging
+CONF = cfg.CONF
+
LOG = logging.getLogger(__name__)
@@ -45,6 +49,14 @@
'ID', 'Name', 'Disk Format', 'Container Format',
'Size', 'Status'])
+ def test_glance_member_list(self):
+ tenant_name = '--tenant-id %s' % self.identity.admin_tenant_name
+ out = self.glance('member-list',
+ params=tenant_name)
+ endpoints = self.parser.listing(out)
+ self.assertTableStruct(endpoints,
+ ['Image ID', 'Member ID', 'Can Share'])
+
def test_glance_help(self):
help_text = self.glance('help')
lines = help_text.split('\n')
@@ -64,3 +76,14 @@
'member-add', 'member-create', 'member-delete',
'member-list'))
self.assertFalse(wanted_commands - commands)
+
+ # Optional arguments:
+
+ def test_glance_version(self):
+ self.glance('', flags='--version')
+
+ def test_glance_debug_list(self):
+ self.glance('image-list', flags='--debug')
+
+ def test_glance_timeout(self):
+ self.glance('image-list', flags='--timeout %d' % CONF.cli.timeout)
diff --git a/tempest/cli/simple_read_only/test_neutron.py b/tempest/cli/simple_read_only/test_neutron.py
index ae3a1a7..047b17d 100644
--- a/tempest/cli/simple_read_only/test_neutron.py
+++ b/tempest/cli/simple_read_only/test_neutron.py
@@ -22,6 +22,7 @@
import tempest.cli
from tempest.openstack.common import log as logging
+from tempest import test
CONF = cfg.CONF
@@ -67,6 +68,33 @@
def test_neutron_floatingip_list(self):
self.neutron('floatingip-list')
+ @test.skip_because(bug="1240694")
+ def test_neutron_meter_label_list(self):
+ self.neutron('meter-label-list')
+
+ @test.skip_because(bug="1240694")
+ def test_neutron_meter_label_rule_list(self):
+ self.neutron('meter-label-rule-list')
+
+ def _test_neutron_lbaas_command(self, command):
+ try:
+ self.neutron(command)
+ except tempest.cli.CommandFailed as e:
+ if '404 Not Found' not in e.stderr:
+ self.fail('%s: Unexpected failure.' % command)
+
+ def test_neutron_lb_healthmonitor_list(self):
+ self._test_neutron_lbaas_command('lb-healthmonitor-list')
+
+ def test_neutron_lb_member_list(self):
+ self._test_neutron_lbaas_command('lb-member-list')
+
+ def test_neutron_lb_pool_list(self):
+ self._test_neutron_lbaas_command('lb-pool-list')
+
+ def test_neutron_lb_vip_list(self):
+ self._test_neutron_lbaas_command('lb-vip-list')
+
def test_neutron_net_external_list(self):
self.neutron('net-external-list')
diff --git a/tempest/cli/simple_read_only/test_compute.py b/tempest/cli/simple_read_only/test_nova.py
similarity index 96%
rename from tempest/cli/simple_read_only/test_compute.py
rename to tempest/cli/simple_read_only/test_nova.py
index 94415f0..1aa8f2c 100644
--- a/tempest/cli/simple_read_only/test_compute.py
+++ b/tempest/cli/simple_read_only/test_nova.py
@@ -22,6 +22,7 @@
import tempest.cli
from tempest.openstack.common import log as logging
+import tempest.test
CONF = cfg.CONF
@@ -73,7 +74,7 @@
def test_admin_dns_domains(self):
self.nova('dns-domains')
- @testtools.skip("Test needs parameters, Bug #1157349")
+ @tempest.test.skip_because(bug="1157349")
def test_admin_dns_list(self):
self.nova('dns-list')
@@ -111,7 +112,7 @@
def test_admin_image_list(self):
self.nova('image-list')
- @testtools.skip("Test needs parameters, Bug #1157349")
+ @tempest.test.skip_because(bug="1157349")
def test_admin_interface_list(self):
self.nova('interface-list')
@@ -136,7 +137,7 @@
def test_admin_secgroup_list(self):
self.nova('secgroup-list')
- @testtools.skip("Test needs parameters, Bug #1157349")
+ @tempest.test.skip_because(bug="1157349")
def test_admin_secgroup_list_rules(self):
self.nova('secgroup-list-rules')
diff --git a/tempest/cli/simple_read_only/test_compute_manage.py b/tempest/cli/simple_read_only/test_nova_manage.py
similarity index 91%
rename from tempest/cli/simple_read_only/test_compute_manage.py
rename to tempest/cli/simple_read_only/test_nova_manage.py
index 9a33556..524db5d 100644
--- a/tempest/cli/simple_read_only/test_compute_manage.py
+++ b/tempest/cli/simple_read_only/test_nova_manage.py
@@ -55,11 +55,11 @@
self.nova_manage('', '--version', merge_stderr=True))
def test_debug_flag(self):
- self.assertNotEqual("", self.nova_manage('instance_type list',
+ self.assertNotEqual("", self.nova_manage('flavor list',
'--debug'))
def test_verbose_flag(self):
- self.assertNotEqual("", self.nova_manage('instance_type list',
+ self.assertNotEqual("", self.nova_manage('flavor list',
'--verbose'))
# test actions
@@ -68,8 +68,6 @@
def test_flavor_list(self):
self.assertNotEqual("", self.nova_manage('flavor list'))
- self.assertEqual(self.nova_manage('instance_type list'),
- self.nova_manage('flavor list'))
def test_db_archive_deleted_rows(self):
# make sure command doesn't error out
diff --git a/tempest/clients.py b/tempest/clients.py
index 63ce1ba..0f54fc0 100644
--- a/tempest/clients.py
+++ b/tempest/clients.py
@@ -23,6 +23,8 @@
AggregatesClientJSON
from tempest.services.compute.json.availability_zone_client import \
AvailabilityZoneClientJSON
+from tempest.services.compute.json.certificates_client import \
+ CertificatesClientJSON
from tempest.services.compute.json.extensions_client import \
ExtensionsClientJSON
from tempest.services.compute.json.fixed_ips_client import FixedIPsClientJSON
@@ -33,6 +35,8 @@
from tempest.services.compute.json.hypervisor_client import \
HypervisorClientJSON
from tempest.services.compute.json.images_client import ImagesClientJSON
+from tempest.services.compute.json.instance_usage_audit_log_client import \
+ InstanceUsagesAuditLogClientJSON
from tempest.services.compute.json.interfaces_client import \
InterfacesClientJSON
from tempest.services.compute.json.keypairs_client import KeyPairsClientJSON
@@ -46,9 +50,30 @@
TenantUsagesClientJSON
from tempest.services.compute.json.volumes_extensions_client import \
VolumesExtensionsClientJSON
+from tempest.services.compute.v3.json.availability_zone_client import \
+ AvailabilityZoneV3ClientJSON
+from tempest.services.compute.v3.json.extensions_client import \
+ ExtensionsV3ClientJSON
+from tempest.services.compute.v3.json.interfaces_client import \
+ InterfacesV3ClientJSON
+from tempest.services.compute.v3.json.servers_client import \
+ ServersV3ClientJSON
+from tempest.services.compute.v3.json.services_client import \
+ ServicesV3ClientJSON
+from tempest.services.compute.v3.xml.availability_zone_client import \
+ AvailabilityZoneV3ClientXML
+from tempest.services.compute.v3.xml.extensions_client import \
+ ExtensionsV3ClientXML
+from tempest.services.compute.v3.xml.interfaces_client import \
+ InterfacesV3ClientXML
+from tempest.services.compute.v3.xml.servers_client import ServersV3ClientXML
+from tempest.services.compute.v3.xml.services_client import \
+ ServicesV3ClientXML
from tempest.services.compute.xml.aggregates_client import AggregatesClientXML
from tempest.services.compute.xml.availability_zone_client import \
AvailabilityZoneClientXML
+from tempest.services.compute.xml.certificates_client import \
+ CertificatesClientXML
from tempest.services.compute.xml.extensions_client import ExtensionsClientXML
from tempest.services.compute.xml.fixed_ips_client import FixedIPsClientXML
from tempest.services.compute.xml.flavors_client import FlavorsClientXML
@@ -56,6 +81,8 @@
FloatingIPsClientXML
from tempest.services.compute.xml.hypervisor_client import HypervisorClientXML
from tempest.services.compute.xml.images_client import ImagesClientXML
+from tempest.services.compute.xml.instance_usage_audit_log_client import \
+ InstanceUsagesAuditLogClientXML
from tempest.services.compute.xml.interfaces_client import \
InterfacesClientXML
from tempest.services.compute.xml.keypairs_client import KeyPairsClientXML
@@ -116,151 +143,6 @@
LOG = logging.getLogger(__name__)
-IMAGES_CLIENTS = {
- "json": ImagesClientJSON,
- "xml": ImagesClientXML,
-}
-
-NETWORKS_CLIENTS = {
- "json": NetworkClientJSON,
- "xml": NetworkClientXML,
-}
-
-KEYPAIRS_CLIENTS = {
- "json": KeyPairsClientJSON,
- "xml": KeyPairsClientXML,
-}
-
-QUOTAS_CLIENTS = {
- "json": QuotasClientJSON,
- "xml": QuotasClientXML,
-}
-
-SERVERS_CLIENTS = {
- "json": ServersClientJSON,
- "xml": ServersClientXML,
-}
-
-LIMITS_CLIENTS = {
- "json": LimitsClientJSON,
- "xml": LimitsClientXML,
-}
-
-FLAVORS_CLIENTS = {
- "json": FlavorsClientJSON,
- "xml": FlavorsClientXML
-}
-
-EXTENSIONS_CLIENTS = {
- "json": ExtensionsClientJSON,
- "xml": ExtensionsClientXML
-}
-
-VOLUMES_EXTENSIONS_CLIENTS = {
- "json": VolumesExtensionsClientJSON,
- "xml": VolumesExtensionsClientXML,
-}
-
-FLOAT_CLIENTS = {
- "json": FloatingIPsClientJSON,
- "xml": FloatingIPsClientXML,
-}
-
-SNAPSHOTS_CLIENTS = {
- "json": SnapshotsClientJSON,
- "xml": SnapshotsClientXML,
-}
-
-VOLUMES_CLIENTS = {
- "json": VolumesClientJSON,
- "xml": VolumesClientXML,
-}
-
-VOLUME_TYPES_CLIENTS = {
- "json": VolumeTypesClientJSON,
- "xml": VolumeTypesClientXML,
-}
-
-IDENTITY_CLIENT = {
- "json": IdentityClientJSON,
- "xml": IdentityClientXML,
-}
-
-IDENTITY_V3_CLIENT = {
- "json": IdentityV3ClientJSON,
- "xml": IdentityV3ClientXML,
-}
-
-TOKEN_CLIENT = {
- "json": TokenClientJSON,
- "xml": TokenClientXML,
-}
-
-SECURITY_GROUPS_CLIENT = {
- "json": SecurityGroupsClientJSON,
- "xml": SecurityGroupsClientXML,
-}
-
-INTERFACES_CLIENT = {
- "json": InterfacesClientJSON,
- "xml": InterfacesClientXML,
-}
-
-ENDPOINT_CLIENT = {
- "json": EndPointClientJSON,
- "xml": EndPointClientXML,
-}
-
-FIXED_IPS_CLIENT = {
- "json": FixedIPsClientJSON,
- "xml": FixedIPsClientXML
-}
-
-AVAILABILITY_ZONE_CLIENT = {
- "json": AvailabilityZoneClientJSON,
- "xml": AvailabilityZoneClientXML,
-}
-
-SERVICE_CLIENT = {
- "json": ServiceClientJSON,
- "xml": ServiceClientXML,
-}
-
-AGGREGATES_CLIENT = {
- "json": AggregatesClientJSON,
- "xml": AggregatesClientXML,
-}
-
-SERVICES_CLIENT = {
- "json": ServicesClientJSON,
- "xml": ServicesClientXML,
-}
-
-TENANT_USAGES_CLIENT = {
- "json": TenantUsagesClientJSON,
- "xml": TenantUsagesClientXML,
-}
-
-POLICY_CLIENT = {
- "json": PolicyClientJSON,
- "xml": PolicyClientXML,
-}
-
-HYPERVISOR_CLIENT = {
- "json": HypervisorClientJSON,
- "xml": HypervisorClientXML,
-}
-
-V3_TOKEN_CLIENT = {
- "json": V3TokenClientJSON,
- "xml": V3TokenClientXML,
-}
-
-CREDENTIALS_CLIENT = {
- "json": CredentialsClientJSON,
- "xml": CredentialsClientXML,
-}
-
class Manager(object):
@@ -308,55 +190,106 @@
else:
client_args_v3_auth = None
- try:
- self.servers_client = SERVERS_CLIENTS[interface](*client_args)
- self.network_client = NETWORKS_CLIENTS[interface](*client_args)
- self.limits_client = LIMITS_CLIENTS[interface](*client_args)
- if self.config.service_available.glance:
- self.images_client = IMAGES_CLIENTS[interface](*client_args)
- self.keypairs_client = KEYPAIRS_CLIENTS[interface](*client_args)
- self.quotas_client = QUOTAS_CLIENTS[interface](*client_args)
- self.flavors_client = FLAVORS_CLIENTS[interface](*client_args)
- ext_cli = EXTENSIONS_CLIENTS[interface](*client_args)
- self.extensions_client = ext_cli
- vol_ext_cli = VOLUMES_EXTENSIONS_CLIENTS[interface](*client_args)
- self.volumes_extensions_client = vol_ext_cli
- self.floating_ips_client = FLOAT_CLIENTS[interface](*client_args)
- self.snapshots_client = SNAPSHOTS_CLIENTS[interface](*client_args)
- self.volumes_client = VOLUMES_CLIENTS[interface](*client_args)
- self.volume_types_client = \
- VOLUME_TYPES_CLIENTS[interface](*client_args)
- self.identity_client = IDENTITY_CLIENT[interface](*client_args)
- self.identity_v3_client = \
- IDENTITY_V3_CLIENT[interface](*client_args)
- self.token_client = TOKEN_CLIENT[interface](self.config)
- self.security_groups_client = \
- SECURITY_GROUPS_CLIENT[interface](*client_args)
- self.interfaces_client = INTERFACES_CLIENT[interface](*client_args)
- self.endpoints_client = ENDPOINT_CLIENT[interface](*client_args)
- self.fixed_ips_client = FIXED_IPS_CLIENT[interface](*client_args)
- self.availability_zone_client = \
- AVAILABILITY_ZONE_CLIENT[interface](*client_args)
- self.service_client = SERVICE_CLIENT[interface](*client_args)
- self.aggregates_client = AGGREGATES_CLIENT[interface](*client_args)
- self.services_client = SERVICES_CLIENT[interface](*client_args)
- self.tenant_usages_client = \
- TENANT_USAGES_CLIENT[interface](*client_args)
- self.policy_client = POLICY_CLIENT[interface](*client_args)
- self.hypervisor_client = HYPERVISOR_CLIENT[interface](*client_args)
- self.token_v3_client = V3_TOKEN_CLIENT[interface](*client_args)
- self.credentials_client = \
- CREDENTIALS_CLIENT[interface](*client_args)
+ self.servers_client_v3_auth = None
+
+ if interface == 'xml':
+ self.certificates_client = CertificatesClientXML(*client_args)
+ self.servers_client = ServersClientXML(*client_args)
+ self.servers_v3_client = ServersV3ClientXML(*client_args)
+ self.limits_client = LimitsClientXML(*client_args)
+ self.images_client = ImagesClientXML(*client_args)
+ self.keypairs_client = KeyPairsClientXML(*client_args)
+ self.quotas_client = QuotasClientXML(*client_args)
+ self.flavors_client = FlavorsClientXML(*client_args)
+ self.extensions_v3_client = ExtensionsV3ClientXML(*client_args)
+ self.extensions_client = ExtensionsClientXML(*client_args)
+ self.volumes_extensions_client = VolumesExtensionsClientXML(
+ *client_args)
+ self.floating_ips_client = FloatingIPsClientXML(*client_args)
+ self.snapshots_client = SnapshotsClientXML(*client_args)
+ self.volumes_client = VolumesClientXML(*client_args)
+ self.volume_types_client = VolumeTypesClientXML(*client_args)
+ self.identity_client = IdentityClientXML(*client_args)
+ self.identity_v3_client = IdentityV3ClientXML(*client_args)
+ self.token_client = TokenClientXML(self.config)
+ self.security_groups_client = SecurityGroupsClientXML(
+ *client_args)
+ self.interfaces_v3_client = InterfacesV3ClientXML(*client_args)
+ self.interfaces_client = InterfacesClientXML(*client_args)
+ self.endpoints_client = EndPointClientXML(*client_args)
+ self.fixed_ips_client = FixedIPsClientXML(*client_args)
+ self.availability_zone_v3_client = AvailabilityZoneV3ClientXML(
+ *client_args)
+ self.availability_zone_client = AvailabilityZoneClientXML(
+ *client_args)
+ self.services_v3_client = ServicesV3ClientXML(*client_args)
+ self.service_client = ServiceClientXML(*client_args)
+ self.aggregates_client = AggregatesClientXML(*client_args)
+ self.services_client = ServicesClientXML(*client_args)
+ self.tenant_usages_client = TenantUsagesClientXML(*client_args)
+ self.policy_client = PolicyClientXML(*client_args)
+ self.hypervisor_client = HypervisorClientXML(*client_args)
+ self.token_v3_client = V3TokenClientXML(*client_args)
+ self.network_client = NetworkClientXML(*client_args)
+ self.credentials_client = CredentialsClientXML(*client_args)
+ self.instance_usages_audit_log_client = \
+ InstanceUsagesAuditLogClientXML(*client_args)
if client_args_v3_auth:
- self.servers_client_v3_auth = SERVERS_CLIENTS[interface](
+ self.servers_client_v3_auth = ServersClientXML(
*client_args_v3_auth)
- else:
- self.servers_client_v3_auth = None
- except KeyError:
+ elif interface == 'json':
+ self.certificates_client = CertificatesClientJSON(*client_args)
+ self.servers_client = ServersClientJSON(*client_args)
+ self.servers_v3_client = ServersV3ClientJSON(*client_args)
+ self.limits_client = LimitsClientJSON(*client_args)
+ self.images_client = ImagesClientJSON(*client_args)
+ self.keypairs_client = KeyPairsClientJSON(*client_args)
+ self.quotas_client = QuotasClientJSON(*client_args)
+ self.flavors_client = FlavorsClientJSON(*client_args)
+ self.extensions_v3_client = ExtensionsV3ClientJSON(*client_args)
+ self.extensions_client = ExtensionsClientJSON(*client_args)
+ self.volumes_extensions_client = VolumesExtensionsClientJSON(
+ *client_args)
+ self.floating_ips_client = FloatingIPsClientJSON(*client_args)
+ self.snapshots_client = SnapshotsClientJSON(*client_args)
+ self.volumes_client = VolumesClientJSON(*client_args)
+ self.volume_types_client = VolumeTypesClientJSON(*client_args)
+ self.identity_client = IdentityClientJSON(*client_args)
+ self.identity_v3_client = IdentityV3ClientJSON(*client_args)
+ self.token_client = TokenClientJSON(self.config)
+ self.security_groups_client = SecurityGroupsClientJSON(
+ *client_args)
+ self.interfaces_v3_client = InterfacesV3ClientJSON(*client_args)
+ self.interfaces_client = InterfacesClientJSON(*client_args)
+ self.endpoints_client = EndPointClientJSON(*client_args)
+ self.fixed_ips_client = FixedIPsClientJSON(*client_args)
+ self.availability_zone_v3_client = AvailabilityZoneV3ClientJSON(
+ *client_args)
+ self.availability_zone_client = AvailabilityZoneClientJSON(
+ *client_args)
+ self.services_v3_client = ServicesV3ClientJSON(*client_args)
+ self.service_client = ServiceClientJSON(*client_args)
+ self.aggregates_client = AggregatesClientJSON(*client_args)
+ self.services_client = ServicesClientJSON(*client_args)
+ self.tenant_usages_client = TenantUsagesClientJSON(*client_args)
+ self.policy_client = PolicyClientJSON(*client_args)
+ self.hypervisor_client = HypervisorClientJSON(*client_args)
+ self.token_v3_client = V3TokenClientJSON(*client_args)
+ self.network_client = NetworkClientJSON(*client_args)
+ self.credentials_client = CredentialsClientJSON(*client_args)
+ self.instance_usages_audit_log_client = \
+ InstanceUsagesAuditLogClientJSON(*client_args)
+
+ if client_args_v3_auth:
+ self.servers_client_v3_auth = ServersClientJSON(
+ *client_args_v3_auth)
+ else:
msg = "Unsupported interface type `%s'" % interface
raise exceptions.InvalidConfiguration(msg)
+
+ # common clients
self.hosts_client = HostsClientJSON(*client_args)
self.account_client = AccountClient(*client_args)
if self.config.service_available.glance:
diff --git a/tempest/common/custom_matchers.py b/tempest/common/custom_matchers.py
new file mode 100644
index 0000000..307d5db
--- /dev/null
+++ b/tempest/common/custom_matchers.py
@@ -0,0 +1,154 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 NTT Corporation
+#
+# 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 re
+
+
+class ExistsAllResponseHeaders(object):
+ """
+ Specific matcher to check the existence of Swift's response headers
+
+ This matcher checks the existence of common headers for each HTTP method
+ or the target, which means account, container or object.
+ When checking the existence of 'specific' headers such as
+ X-Account-Meta-* or X-Object-Manifest for example, those headers must be
+ checked in each test code.
+ """
+
+ def __init__(self, target, method):
+ """
+ param: target Account/Container/Object
+ param: method PUT/GET/HEAD/DELETE/COPY/POST
+ """
+ self.target = target
+ self.method = method
+
+ def match(self, actual):
+ """
+ param: actual HTTP response headers
+ """
+ # Check common headers for all HTTP methods
+ if 'content-length' not in actual:
+ return NonExistentHeader('content-length')
+ if 'content-type' not in actual:
+ return NonExistentHeader('content-type')
+ if 'x-trans-id' not in actual:
+ return NonExistentHeader('x-trans-id')
+ if 'date' not in actual:
+ return NonExistentHeader('date')
+
+ # Check headers for a specific method or target
+ if self.method == 'GET' or self.method == 'HEAD':
+ if 'x-timestamp' not in actual:
+ return NonExistentHeader('x-timestamp')
+ if 'accept-ranges' not in actual:
+ return NonExistentHeader('accept-ranges')
+ if self.target == 'Account':
+ if 'x-account-bytes-used' not in actual:
+ return NonExistentHeader('x-account-bytes-used')
+ if 'x-account-container-count' not in actual:
+ return NonExistentHeader('x-account-container-count')
+ if 'x-account-object-count' not in actual:
+ return NonExistentHeader('x-account-object-count')
+ elif self.target == 'Container':
+ if 'x-container-bytes-used' not in actual:
+ return NonExistentHeader('x-container-bytes-used')
+ if 'x-container-object-count' not in actual:
+ return NonExistentHeader('x-container-object-count')
+ elif self.target == 'Object':
+ if 'etag' not in actual:
+ return NonExistentHeader('etag')
+ elif self.method == 'PUT' or self.method == 'COPY':
+ if self.target == 'Object':
+ if 'etag' not in actual:
+ return NonExistentHeader('etag')
+
+ return None
+
+
+class NonExistentHeader(object):
+ """
+ Informs an error message for end users in the case of missing a
+ certain header in Swift's responses
+ """
+
+ def __init__(self, header):
+ self.header = header
+
+ def describe(self):
+ return "%s header does not exist" % self.header
+
+ def get_details(self):
+ return {}
+
+
+class AreAllWellFormatted(object):
+ """
+ Specific matcher to check the correctness of formats of values of Swift's
+ response headers
+
+ This matcher checks the format of values of response headers.
+ When checking the format of values of 'specific' headers such as
+ X-Account-Meta-* or X-Object-Manifest for example, those values must be
+ checked in each test code.
+ """
+
+ def match(self, actual):
+ for key, value in actual.iteritems():
+ if key == 'content-length' and not value.isdigit():
+ return InvalidFormat(key, value)
+ elif key == 'x-timestamp' and not re.match("^\d+\.?\d*\Z", value):
+ return InvalidFormat(key, value)
+ elif key == 'x-account-bytes-used' and not value.isdigit():
+ return InvalidFormat(key, value)
+ elif key == 'x-account-container-count' and not value.isdigit():
+ return InvalidFormat(key, value)
+ elif key == 'x-account-object-count' and not value.isdigit():
+ return InvalidFormat(key, value)
+ elif key == 'x-container-bytes-used' and not value.isdigit():
+ return InvalidFormat(key, value)
+ elif key == 'x-container-object-count' and not value.isdigit():
+ return InvalidFormat(key, value)
+ elif key == 'content-type' and not value:
+ return InvalidFormat(key, value)
+ elif key == 'x-trans-id' and \
+ not re.match("^tx[0-9a-f]*-[0-9a-f]*$", value):
+ return InvalidFormat(key, value)
+ elif key == 'date' and not value:
+ return InvalidFormat(key, value)
+ elif key == 'accept-ranges' and not value == 'bytes':
+ return InvalidFormat(key, value)
+ elif key == 'etag' and not value.isalnum():
+ return InvalidFormat(key, value)
+
+ return None
+
+
+class InvalidFormat(object):
+ """
+ Informs an error message for end users if a format of a certain header
+ is invalid
+ """
+
+ def __init__(self, key, value):
+ self.key = key
+ self.value = value
+
+ def describe(self):
+ return "InvalidFormat (%s, %s)" % (self.key, self.value)
+
+ def get_details(self):
+ return {}
diff --git a/tempest/common/generate_sample_tempest.py b/tempest/common/generate_sample_tempest.py
new file mode 100644
index 0000000..545703b
--- /dev/null
+++ b/tempest/common/generate_sample_tempest.py
@@ -0,0 +1,35 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 IBM Corp.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+
+import sys
+
+from tempest import config
+from tempest.openstack.common.config import generator
+
+# NOTE(mtreinish): This hack is needed because of how oslo config is used in
+# tempest. Tempest is run from inside a test runner and so we can't rely on the
+# global CONF object being fully populated when we run a test. (test runners
+# don't init every file for running a test) So to get around that we manually
+# load the config file in tempest for each test class to ensure that every
+# config option is set. However, the tool expects the CONF object to be fully
+# populated when it inits all the files in the project. This just works around
+# the issue by manually loading the config file (which may or may not exist)
+# which will populate all the options before running the generator.
+
+config.TempestConfig()
+generator.generate(sys.argv[1:])
diff --git a/tempest/common/isolated_creds.py b/tempest/common/isolated_creds.py
index 8c82ec0..5dbb3a7 100644
--- a/tempest/common/isolated_creds.py
+++ b/tempest/common/isolated_creds.py
@@ -20,7 +20,7 @@
import neutronclient.v2_0.client as neutronclient
from tempest import clients
-from tempest.common.utils.data_utils import rand_name
+from tempest.common.utils import data_utils
from tempest import config
from tempest import exceptions
from tempest.openstack.common import log as logging
@@ -147,17 +147,17 @@
self.identity_admin_client.tenants.delete(tenant)
def _create_creds(self, suffix=None, admin=False):
- rand_name_root = rand_name(self.name)
+ data_utils.rand_name_root = data_utils.rand_name(self.name)
if suffix:
- rand_name_root += suffix
- tenant_name = rand_name_root + "-tenant"
+ data_utils.rand_name_root += suffix
+ tenant_name = data_utils.rand_name_root + "-tenant"
tenant_desc = tenant_name + "-desc"
tenant = self._create_tenant(name=tenant_name,
description=tenant_desc)
if suffix:
- rand_name_root += suffix
- username = rand_name_root + "-user"
- email = rand_name_root + "@example.com"
+ data_utils.rand_name_root += suffix
+ username = data_utils.rand_name_root + "-user"
+ email = data_utils.rand_name_root + "@example.com"
user = self._create_user(username, self.password,
tenant, email)
if admin:
@@ -197,13 +197,13 @@
network = None
subnet = None
router = None
- rand_name_root = rand_name(self.name)
- network_name = rand_name_root + "-network"
+ data_utils.rand_name_root = data_utils.rand_name(self.name)
+ network_name = data_utils.rand_name_root + "-network"
network = self._create_network(network_name, tenant_id)
try:
- subnet_name = rand_name_root + "-subnet"
+ subnet_name = data_utils.rand_name_root + "-subnet"
subnet = self._create_subnet(subnet_name, tenant_id, network['id'])
- router_name = rand_name_root + "-router"
+ router_name = data_utils.rand_name_root + "-router"
router = self._create_router(router_name, tenant_id)
self._add_router_interface(router['id'], subnet['id'])
except Exception:
@@ -412,7 +412,11 @@
resp_body = self.network_admin_client.list_ports()
self.ports = resp_body['ports']
ports_to_delete = [
- port for port in self.ports if port['network_id'] == network_id]
+ port
+ for port in self.ports
+ if (port['network_id'] == network_id and
+ port['device_owner'] != 'network:router_interface')
+ ]
for port in ports_to_delete:
try:
LOG.info('Cleaning up port id %s, name %s' %
diff --git a/tempest/common/rest_client.py b/tempest/common/rest_client.py
index 4b5127a..9322f1b 100644
--- a/tempest/common/rest_client.py
+++ b/tempest/common/rest_client.py
@@ -488,7 +488,7 @@
if resp.status == 409:
if parse_resp:
resp_body = self._parse_resp(resp_body)
- raise exceptions.Duplicate(resp_body)
+ raise exceptions.Conflict(resp_body)
if resp.status == 413:
if parse_resp:
@@ -519,7 +519,7 @@
elif 'message' in resp_body:
message = resp_body['message']
- raise exceptions.ComputeFault(message)
+ raise exceptions.ServerFault(message)
if resp.status >= 400:
if parse_resp:
diff --git a/tempest/common/ssh.py b/tempest/common/ssh.py
index 3eaa203..742a354 100644
--- a/tempest/common/ssh.py
+++ b/tempest/common/ssh.py
@@ -66,7 +66,8 @@
_timeout = False
break
except (socket.error,
- paramiko.AuthenticationException):
+ paramiko.AuthenticationException,
+ paramiko.SSHException):
time.sleep(bsleep)
bsleep *= backoff
continue
diff --git a/tempest/common/tempest_fixtures.py b/tempest/common/tempest_fixtures.py
index 081b271..73c02e8 100644
--- a/tempest/common/tempest_fixtures.py
+++ b/tempest/common/tempest_fixtures.py
@@ -15,16 +15,9 @@
# License for the specific language governing permissions and limitations
# under the License.
-import fixtures
-
-from tempest.openstack.common import lockutils
+from tempest.openstack.common.fixture import lockutils
-class LockFixture(fixtures.Fixture):
+class LockFixture(lockutils.LockFixture):
def __init__(self, name):
- self.mgr = lockutils.lock(name, 'tempest-', True)
-
- def setUp(self):
- super(LockFixture, self).setUp()
- self.addCleanup(self.mgr.__exit__, None, None, None)
- self.mgr.__enter__()
+ super(LockFixture, self).__init__(name, 'tempest-')
diff --git a/tempest/common/utils/data_utils.py b/tempest/common/utils/data_utils.py
index bbba235..4f93e1c 100644
--- a/tempest/common/utils/data_utils.py
+++ b/tempest/common/utils/data_utils.py
@@ -19,12 +19,21 @@
import random
import re
import urllib
+import uuid
from tempest import exceptions
+def rand_uuid():
+ return str(uuid.uuid4())
+
+
+def rand_uuid_hex():
+ return uuid.uuid4().hex
+
+
def rand_name(name='test'):
- return name + str(random.randint(1, 0x7fffffff))
+ return name + "-tempest-" + str(random.randint(1, 0x7fffffff))
def rand_int_id(start=0, end=0x7fffffff):
diff --git a/tempest/common/waiters.py b/tempest/common/waiters.py
index 15569cd..bea2cdc 100644
--- a/tempest/common/waiters.py
+++ b/tempest/common/waiters.py
@@ -24,7 +24,8 @@
# NOTE(afazekas): This function needs to know a token and a subject.
-def wait_for_server_status(client, server_id, status, ready_wait=True):
+def wait_for_server_status(client, server_id, status, ready_wait=True,
+ extra_timeout=0):
"""Waits for a server to reach a given status."""
def _get_task_state(body):
@@ -37,6 +38,7 @@
old_status = server_status = body['status']
old_task_state = task_state = _get_task_state(body)
start_time = int(time.time())
+ timeout = client.build_timeout + extra_timeout
while True:
# NOTE(afazekas): Now the BUILD status only reached
# between the UNKOWN->ACTIVE transition.
@@ -70,12 +72,12 @@
if server_status == 'ERROR':
raise exceptions.BuildErrorException(server_id=server_id)
- timed_out = int(time.time()) - start_time >= client.build_timeout
+ timed_out = int(time.time()) - start_time >= timeout
if timed_out:
message = ('Server %s failed to reach %s status within the '
'required time (%s s).' %
- (server_id, status, client.build_timeout))
+ (server_id, status, timeout))
message += ' Current status: %s.' % server_status
raise exceptions.TimeoutException(message)
old_status = server_status
diff --git a/tempest/config.py b/tempest/config.py
index ff0cddb..6af7d51 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -137,28 +137,6 @@
default="password",
help="Password used to authenticate to an instance using "
"the alternate image."),
- cfg.BoolOpt('resize_available',
- default=False,
- help="Does the test environment support resizing?"),
- cfg.BoolOpt('live_migration_available',
- default=False,
- help="Does the test environment support live migration "
- "available?"),
- cfg.BoolOpt('use_block_migration_for_live_migration',
- default=False,
- help="Does the test environment use block devices for live "
- "migration"),
- cfg.BoolOpt('block_migrate_supports_cinder_iscsi',
- default=False,
- help="Does the test environment block migration support "
- "cinder iSCSI volumes"),
- cfg.BoolOpt('change_password_available',
- default=False,
- help="Does the test environment support changing the admin "
- "password?"),
- cfg.BoolOpt('create_image_enabled',
- default=False,
- help="Does the test environment support snapshots?"),
cfg.IntOpt('build_interval',
default=10,
help="Time in seconds between build status checks."),
@@ -208,22 +186,64 @@
"of identity.region is used instead. If no such region "
"is found in the service catalog, the first found one is "
"used."),
+ cfg.StrOpt('catalog_v3_type',
+ default='computev3',
+ help="Catalog type of the Compute v3 service."),
cfg.StrOpt('path_to_private_key',
default=None,
help="Path to a private key file for SSH access to remote "
"hosts"),
- cfg.BoolOpt('disk_config_enabled',
- default=True,
- help="If false, skip disk config tests"),
- cfg.BoolOpt('flavor_extra_enabled',
- default=True,
- help="If false, skip flavor extra data test"),
cfg.StrOpt('volume_device_name',
default='vdb',
help="Expected device name when a volume is attached to "
- "an instance")
+ "an instance"),
+ cfg.IntOpt('shelved_offload_time',
+ default=0,
+ help='Time in seconds before a shelved instance is eligible '
+ 'for removing from a host. -1 never offload, 0 offload '
+ 'when shelved. This time should be the same as the time '
+ 'of nova.conf, and some tests will run for as long as the '
+ 'time.')
]
+compute_features_group = cfg.OptGroup(name='compute-feature-enabled',
+ title="Enabled Compute Service Features")
+
+ComputeFeaturesGroup = [
+ cfg.BoolOpt('api_v3',
+ default=True,
+ help="If false, skip all nova v3 tests."),
+ cfg.BoolOpt('disk_config',
+ default=True,
+ help="If false, skip disk config tests"),
+ cfg.BoolOpt('flavor_extra',
+ default=True,
+ help="If false, skip flavor extra data test"),
+ cfg.BoolOpt('change_password',
+ default=False,
+ help="Does the test environment support changing the admin "
+ "password?"),
+ cfg.BoolOpt('create_image',
+ default=False,
+ help="Does the test environment support snapshots?"),
+ cfg.BoolOpt('resize',
+ default=False,
+ help="Does the test environment support resizing?"),
+ cfg.BoolOpt('live_migration',
+ default=False,
+ help="Does the test environment support live migration "
+ "available?"),
+ cfg.BoolOpt('block_migration_for_live_migration',
+ default=False,
+ help="Does the test environment use block devices for live "
+ "migration"),
+ cfg.BoolOpt('block_migrate_cinder_iscsi',
+ default=False,
+ help="Does the test environment block migration support "
+ "cinder iSCSI volumes")
+]
+
+
compute_admin_group = cfg.OptGroup(name='compute-admin',
title="Compute Admin Options")
@@ -245,9 +265,6 @@
title="Image Service Options")
ImageGroup = [
- cfg.StrOpt('api_version',
- default='1',
- help="Version of the API"),
cfg.StrOpt('catalog_type',
default='image',
help='Catalog type of the Image service.'),
@@ -263,6 +280,17 @@
help='http accessible image')
]
+image_feature_group = cfg.OptGroup(name='image-feature-enabled',
+ title='Enabled image service features')
+
+ImageFeaturesGroup = [
+ cfg.BoolOpt('api_v2',
+ default=True,
+ help="Is the v2 image API enabled"),
+ cfg.BoolOpt('api_v1',
+ default=True,
+ help="Is the v1 image API enabled"),
+]
network_group = cfg.OptGroup(name='network',
title='Network Service Options')
@@ -309,7 +337,7 @@
help='Timeout in seconds to wait for a volume to become'
'available.'),
cfg.StrOpt('catalog_type',
- default='Volume',
+ default='volume',
help="Catalog type of the Volume Service"),
cfg.StrOpt('region',
default='',
@@ -317,9 +345,6 @@
"of identity.region is used instead. If no such region "
"is found in the service catalog, the first found one is "
"used."),
- cfg.BoolOpt('multi_backend_enabled',
- default=False,
- help="Runs Cinder multi-backend test (requires 2 backends)"),
cfg.StrOpt('backend1_name',
default='BACKEND_1',
help="Name of the backend1 (must be declared in cinder.conf)"),
@@ -337,6 +362,15 @@
help='Disk format to use when copying a volume to image'),
]
+volume_feature_group = cfg.OptGroup(name='volume-feature-enabled',
+ title='Enabled Cinder Features')
+
+VolumeFeaturesGroup = [
+ cfg.BoolOpt('multi_backend',
+ default=False,
+ help="Runs Cinder multi-backend test (requires 2 backends)")
+]
+
object_storage_group = cfg.OptGroup(name='object-storage',
title='Object Storage Service Options')
@@ -351,27 +385,37 @@
"value of identity.region is used instead. If no such "
"region is found in the service catalog, the first found "
"one is used."),
- cfg.StrOpt('container_sync_timeout',
+ cfg.IntOpt('container_sync_timeout',
default=120,
help="Number of seconds to time on waiting for a container"
"to container synchronization complete."),
- cfg.StrOpt('container_sync_interval',
+ cfg.IntOpt('container_sync_interval',
default=5,
help="Number of seconds to wait while looping to check the"
"status of a container to container synchronization"),
- cfg.BoolOpt('accounts_quotas_available',
- default=True,
- help="Set to True if the Account Quota middleware is enabled"),
- cfg.BoolOpt('container_quotas_available',
- default=True,
- help="Set to True if the container quota middleware "
- "is enabled"),
cfg.StrOpt('operator_role',
default='Member',
help="Role to add to users created for swift tests to "
"enable creating containers"),
]
+object_storage_feature_group = cfg.OptGroup(
+ name='object-storage-feature-enabled',
+ title='Enabled object-storage features')
+
+ObjectStoreFeaturesGroup = [
+ cfg.BoolOpt('container_quotas',
+ default=True,
+ help="Set to True if the container quota middleware "
+ "is enabled"),
+ cfg.BoolOpt('accounts_quotas',
+ default=True,
+ help="Set to True if the Account Quota middleware is enabled"),
+ cfg.BoolOpt('crossdomain',
+ default=True,
+ help="Set to True if the Crossdomain middleware is enabled"),
+]
+
orchestration_group = cfg.OptGroup(name='orchestration',
title='Orchestration Service Options')
@@ -410,7 +454,7 @@
default=None,
help="Name of existing keypair to launch servers with."),
cfg.IntOpt('max_template_size',
- default=10240,
+ default=524288,
help="Value must match heat configuration of the same name."),
]
@@ -499,10 +543,10 @@
cfg.StrOpt('target_logfiles',
default=None,
help='regexp for list of log files.'),
- cfg.StrOpt('log_check_interval',
+ cfg.IntOpt('log_check_interval',
default=60,
help='time (in seconds) between log file error checks.'),
- cfg.StrOpt('default_thread_number_per_action',
+ cfg.IntOpt('default_thread_number_per_action',
default=4,
help='The number of threads created while stress test.')
]
@@ -557,6 +601,9 @@
cfg.BoolOpt('heat',
default=False,
help="Whether or not Heat is expected to be available"),
+ cfg.BoolOpt('ceilometer',
+ default=True,
+ help="Whether or not Ceilometer is expected to be available"),
cfg.BoolOpt('horizon',
default=True,
help="Whether or not Horizon is expected to be available"),
@@ -611,11 +658,18 @@
LOG.info("Using tempest config file %s" % path)
register_opt_group(cfg.CONF, compute_group, ComputeGroup)
+ register_opt_group(cfg.CONF, compute_features_group,
+ ComputeFeaturesGroup)
register_opt_group(cfg.CONF, identity_group, IdentityGroup)
register_opt_group(cfg.CONF, image_group, ImageGroup)
+ register_opt_group(cfg.CONF, image_feature_group, ImageFeaturesGroup)
register_opt_group(cfg.CONF, network_group, NetworkGroup)
register_opt_group(cfg.CONF, volume_group, VolumeGroup)
+ register_opt_group(cfg.CONF, volume_feature_group,
+ VolumeFeaturesGroup)
register_opt_group(cfg.CONF, object_storage_group, ObjectStoreGroup)
+ register_opt_group(cfg.CONF, object_storage_feature_group,
+ ObjectStoreFeaturesGroup)
register_opt_group(cfg.CONF, orchestration_group, OrchestrationGroup)
register_opt_group(cfg.CONF, dashboard_group, DashboardGroup)
register_opt_group(cfg.CONF, boto_group, BotoGroup)
@@ -626,11 +680,16 @@
ServiceAvailableGroup)
register_opt_group(cfg.CONF, debug_group, DebugGroup)
self.compute = cfg.CONF.compute
+ self.compute_feature_enabled = cfg.CONF['compute-feature-enabled']
self.identity = cfg.CONF.identity
self.images = cfg.CONF.image
+ self.image_feature_enabled = cfg.CONF['image-feature-enabled']
self.network = cfg.CONF.network
self.volume = cfg.CONF.volume
+ self.volume_feature_enabled = cfg.CONF['volume-feature-enabled']
self.object_storage = cfg.CONF['object-storage']
+ self.object_storage_feature_enabled = cfg.CONF[
+ 'object-storage-feature-enabled']
self.orchestration = cfg.CONF.orchestration
self.dashboard = cfg.CONF.dashboard
self.boto = cfg.CONF.boto
diff --git a/tempest/exceptions.py b/tempest/exceptions.py
index 158a216..02fc231 100644
--- a/tempest/exceptions.py
+++ b/tempest/exceptions.py
@@ -81,6 +81,10 @@
message = "Server %(server_id)s failed to build and is in ERROR status"
+class ImageKilledException(TempestException):
+ message = "Image %(image_id)s 'killed' while waiting for '%(status)s'"
+
+
class AddImageException(TempestException):
message = "Image %(image_id)s failed to become ACTIVE in the allotted time"
@@ -129,8 +133,8 @@
message = "Quota exceeded"
-class ComputeFault(TempestException):
- message = "Got compute fault"
+class ServerFault(TempestException):
+ message = "Got server fault"
class ImageFault(TempestException):
@@ -141,7 +145,7 @@
message = "Got identity error"
-class Duplicate(RestClientException):
+class Conflict(RestClientException):
message = "An object with that identifier already exists"
diff --git a/tempest/openstack/common/config/__init__.py b/tempest/openstack/common/config/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/openstack/common/config/__init__.py
diff --git a/tempest/openstack/common/config/generator.py b/tempest/openstack/common/config/generator.py
new file mode 100644
index 0000000..373f9a6
--- /dev/null
+++ b/tempest/openstack/common/config/generator.py
@@ -0,0 +1,268 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 SINA Corporation
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+
+"""Extracts OpenStack config option info from module(s)."""
+
+from __future__ import print_function
+
+import imp
+import os
+import re
+import socket
+import sys
+import textwrap
+
+from oslo.config import cfg
+
+from tempest.openstack.common import gettextutils
+from tempest.openstack.common import importutils
+
+gettextutils.install('tempest')
+
+STROPT = "StrOpt"
+BOOLOPT = "BoolOpt"
+INTOPT = "IntOpt"
+FLOATOPT = "FloatOpt"
+LISTOPT = "ListOpt"
+MULTISTROPT = "MultiStrOpt"
+
+OPT_TYPES = {
+ STROPT: 'string value',
+ BOOLOPT: 'boolean value',
+ INTOPT: 'integer value',
+ FLOATOPT: 'floating point value',
+ LISTOPT: 'list value',
+ MULTISTROPT: 'multi valued',
+}
+
+OPTION_REGEX = re.compile(r"(%s)" % "|".join([STROPT, BOOLOPT, INTOPT,
+ FLOATOPT, LISTOPT,
+ MULTISTROPT]))
+
+PY_EXT = ".py"
+BASEDIR = os.path.abspath(os.path.join(os.path.dirname(__file__),
+ "../../../../"))
+WORDWRAP_WIDTH = 60
+
+
+def generate(srcfiles):
+ mods_by_pkg = dict()
+ for filepath in srcfiles:
+ pkg_name = filepath.split(os.sep)[1]
+ mod_str = '.'.join(['.'.join(filepath.split(os.sep)[:-1]),
+ os.path.basename(filepath).split('.')[0]])
+ mods_by_pkg.setdefault(pkg_name, list()).append(mod_str)
+ # NOTE(lzyeval): place top level modules before packages
+ pkg_names = filter(lambda x: x.endswith(PY_EXT), mods_by_pkg.keys())
+ pkg_names.sort()
+ ext_names = filter(lambda x: x not in pkg_names, mods_by_pkg.keys())
+ ext_names.sort()
+ pkg_names.extend(ext_names)
+
+ # opts_by_group is a mapping of group name to an options list
+ # The options list is a list of (module, options) tuples
+ opts_by_group = {'DEFAULT': []}
+
+ for module_name in os.getenv(
+ "OSLO_CONFIG_GENERATOR_EXTRA_MODULES", "").split(','):
+ module = _import_module(module_name)
+ if module:
+ for group, opts in _list_opts(module):
+ opts_by_group.setdefault(group, []).append((module_name, opts))
+
+ for pkg_name in pkg_names:
+ mods = mods_by_pkg.get(pkg_name)
+ mods.sort()
+ for mod_str in mods:
+ if mod_str.endswith('.__init__'):
+ mod_str = mod_str[:mod_str.rfind(".")]
+
+ mod_obj = _import_module(mod_str)
+ if not mod_obj:
+ raise RuntimeError("Unable to import module %s" % mod_str)
+
+ for group, opts in _list_opts(mod_obj):
+ opts_by_group.setdefault(group, []).append((mod_str, opts))
+
+ print_group_opts('DEFAULT', opts_by_group.pop('DEFAULT', []))
+ for group, opts in opts_by_group.items():
+ print_group_opts(group, opts)
+
+
+def _import_module(mod_str):
+ try:
+ if mod_str.startswith('bin.'):
+ imp.load_source(mod_str[4:], os.path.join('bin', mod_str[4:]))
+ return sys.modules[mod_str[4:]]
+ else:
+ return importutils.import_module(mod_str)
+ except ImportError as ie:
+ sys.stderr.write("%s\n" % str(ie))
+ return None
+ except Exception:
+ return None
+
+
+def _is_in_group(opt, group):
+ "Check if opt is in group."
+ for key, value in group._opts.items():
+ if value['opt'] == opt:
+ return True
+ return False
+
+
+def _guess_groups(opt, mod_obj):
+ # is it in the DEFAULT group?
+ if _is_in_group(opt, cfg.CONF):
+ return 'DEFAULT'
+
+ # what other groups is it in?
+ for key, value in cfg.CONF.items():
+ if isinstance(value, cfg.CONF.GroupAttr):
+ if _is_in_group(opt, value._group):
+ return value._group.name
+
+ raise RuntimeError(
+ "Unable to find group for option %s, "
+ "maybe it's defined twice in the same group?"
+ % opt.name
+ )
+
+
+def _list_opts(obj):
+ def is_opt(o):
+ return (isinstance(o, cfg.Opt) and
+ not isinstance(o, cfg.SubCommandOpt))
+
+ opts = list()
+ for attr_str in dir(obj):
+ attr_obj = getattr(obj, attr_str)
+ if is_opt(attr_obj):
+ opts.append(attr_obj)
+ elif (isinstance(attr_obj, list) and
+ all(map(lambda x: is_opt(x), attr_obj))):
+ opts.extend(attr_obj)
+
+ ret = {}
+ for opt in opts:
+ ret.setdefault(_guess_groups(opt, obj), []).append(opt)
+ return ret.items()
+
+
+def print_group_opts(group, opts_by_module):
+ print("[%s]" % group)
+ print('')
+ for mod, opts in opts_by_module:
+ print('#')
+ print('# Options defined in %s' % mod)
+ print('#')
+ print('')
+ for opt in opts:
+ _print_opt(opt)
+ print('')
+
+
+def _get_my_ip():
+ try:
+ csock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+ csock.connect(('8.8.8.8', 80))
+ (addr, port) = csock.getsockname()
+ csock.close()
+ return addr
+ except socket.error:
+ return None
+
+
+def _sanitize_default(name, value):
+ """Set up a reasonably sensible default for pybasedir, my_ip and host."""
+ if value.startswith(sys.prefix):
+ # NOTE(jd) Don't use os.path.join, because it is likely to think the
+ # second part is an absolute pathname and therefore drop the first
+ # part.
+ value = os.path.normpath("/usr/" + value[len(sys.prefix):])
+ elif value.startswith(BASEDIR):
+ return value.replace(BASEDIR, '/usr/lib/python/site-packages')
+ elif BASEDIR in value:
+ return value.replace(BASEDIR, '')
+ elif value == _get_my_ip():
+ return '10.0.0.1'
+ elif value == socket.gethostname() and 'host' in name:
+ return 'tempest'
+ elif value.strip() != value:
+ return '"%s"' % value
+ return value
+
+
+def _print_opt(opt):
+ opt_name, opt_default, opt_help = opt.dest, opt.default, opt.help
+ if not opt_help:
+ sys.stderr.write('WARNING: "%s" is missing help string.\n' % opt_name)
+ opt_help = ""
+ opt_type = None
+ try:
+ opt_type = OPTION_REGEX.search(str(type(opt))).group(0)
+ except (ValueError, AttributeError) as err:
+ sys.stderr.write("%s\n" % str(err))
+ sys.exit(1)
+ opt_help += ' (' + OPT_TYPES[opt_type] + ')'
+ print('#', "\n# ".join(textwrap.wrap(opt_help, WORDWRAP_WIDTH)))
+ if opt.deprecated_opts:
+ for deprecated_opt in opt.deprecated_opts:
+ if deprecated_opt.name:
+ deprecated_group = (deprecated_opt.group if
+ deprecated_opt.group else "DEFAULT")
+ print('# Deprecated group/name - [%s]/%s' %
+ (deprecated_group,
+ deprecated_opt.name))
+ try:
+ if opt_default is None:
+ print('#%s=<None>' % opt_name)
+ elif opt_type == STROPT:
+ assert(isinstance(opt_default, basestring))
+ print('#%s=%s' % (opt_name, _sanitize_default(opt_name,
+ opt_default)))
+ elif opt_type == BOOLOPT:
+ assert(isinstance(opt_default, bool))
+ print('#%s=%s' % (opt_name, str(opt_default).lower()))
+ elif opt_type == INTOPT:
+ assert(isinstance(opt_default, int) and
+ not isinstance(opt_default, bool))
+ print('#%s=%s' % (opt_name, opt_default))
+ elif opt_type == FLOATOPT:
+ assert(isinstance(opt_default, float))
+ print('#%s=%s' % (opt_name, opt_default))
+ elif opt_type == LISTOPT:
+ assert(isinstance(opt_default, list))
+ print('#%s=%s' % (opt_name, ','.join(opt_default)))
+ elif opt_type == MULTISTROPT:
+ assert(isinstance(opt_default, list))
+ if not opt_default:
+ opt_default = ['']
+ for default in opt_default:
+ print('#%s=%s' % (opt_name, default))
+ print('')
+ except Exception:
+ sys.stderr.write('Error in option "%s"\n' % opt_name)
+ sys.exit(1)
+
+
+def main():
+ generate(sys.argv[1:])
+
+if __name__ == '__main__':
+ main()
diff --git a/tempest/openstack/common/excutils.py b/tempest/openstack/common/excutils.py
index db37660..c7bce72 100644
--- a/tempest/openstack/common/excutils.py
+++ b/tempest/openstack/common/excutils.py
@@ -79,7 +79,7 @@
try:
return infunc(*args, **kwargs)
except Exception as exc:
- this_exc_message = unicode(exc)
+ this_exc_message = six.u(str(exc))
if this_exc_message == last_exc_message:
exc_count += 1
else:
diff --git a/tempest/openstack/common/fileutils.py b/tempest/openstack/common/fileutils.py
index 6cf68ba..15530af 100644
--- a/tempest/openstack/common/fileutils.py
+++ b/tempest/openstack/common/fileutils.py
@@ -19,6 +19,7 @@
import contextlib
import errno
import os
+import tempfile
from tempest.openstack.common import excutils
from tempest.openstack.common.gettextutils import _ # noqa
@@ -109,3 +110,30 @@
state at all (for unit tests)
"""
return file(*args, **kwargs)
+
+
+def write_to_tempfile(content, path=None, suffix='', prefix='tmp'):
+ """Create temporary file or use existing file.
+
+ This util is needed for creating temporary file with
+ specified content, suffix and prefix. If path is not None,
+ it will be used for writing content. If the path doesn't
+ exist it'll be created.
+
+ :param content: content for temporary file.
+ :param path: same as parameter 'dir' for mkstemp
+ :param suffix: same as parameter 'suffix' for mkstemp
+ :param prefix: same as parameter 'prefix' for mkstemp
+
+ For example: it can be used in database tests for creating
+ configuration files.
+ """
+ if path:
+ ensure_tree(path)
+
+ (fd, path) = tempfile.mkstemp(suffix=suffix, dir=path, prefix=prefix)
+ try:
+ os.write(fd, content)
+ finally:
+ os.close(fd)
+ return path
diff --git a/tempest/openstack/common/fixture/__init__.py b/tempest/openstack/common/fixture/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/openstack/common/fixture/__init__.py
diff --git a/tempest/openstack/common/fixture/config.py b/tempest/openstack/common/fixture/config.py
new file mode 100644
index 0000000..7b044ef
--- /dev/null
+++ b/tempest/openstack/common/fixture/config.py
@@ -0,0 +1,46 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright 2013 Mirantis, Inc.
+# Copyright 2013 OpenStack Foundation
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+import fixtures
+from oslo.config import cfg
+import six
+
+
+class Config(fixtures.Fixture):
+ """Override some configuration values.
+
+ The keyword arguments are the names of configuration options to
+ override and their values.
+
+ If a group argument is supplied, the overrides are applied to
+ the specified configuration option group.
+
+ All overrides are automatically cleared at the end of the current
+ test by the reset() method, which is registred by addCleanup().
+ """
+
+ def __init__(self, conf=cfg.CONF):
+ self.conf = conf
+
+ def setUp(self):
+ super(Config, self).setUp()
+ self.addCleanup(self.conf.reset)
+
+ def config(self, **kw):
+ group = kw.pop('group', None)
+ for k, v in six.iteritems(kw):
+ self.conf.set_override(k, v, group)
diff --git a/tempest/openstack/common/fixture/lockutils.py b/tempest/openstack/common/fixture/lockutils.py
new file mode 100644
index 0000000..21b4a48
--- /dev/null
+++ b/tempest/openstack/common/fixture/lockutils.py
@@ -0,0 +1,53 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2011 OpenStack Foundation.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import fixtures
+
+from tempest.openstack.common.lockutils import lock
+
+
+class LockFixture(fixtures.Fixture):
+ """External locking fixture.
+
+ This fixture is basically an alternative to the synchronized decorator with
+ the external flag so that tearDowns and addCleanups will be included in
+ the lock context for locking between tests. The fixture is recommended to
+ be the first line in a test method, like so::
+
+ def test_method(self):
+ self.useFixture(LockFixture)
+ ...
+
+ or the first line in setUp if all the test methods in the class are
+ required to be serialized. Something like::
+
+ class TestCase(testtools.testcase):
+ def setUp(self):
+ self.useFixture(LockFixture)
+ super(TestCase, self).setUp()
+ ...
+
+ This is because addCleanups are put on a LIFO queue that gets run after the
+ test method exits. (either by completing or raising an exception)
+ """
+ def __init__(self, name, lock_file_prefix=None):
+ self.mgr = lock(name, lock_file_prefix, True)
+
+ def setUp(self):
+ super(LockFixture, self).setUp()
+ self.addCleanup(self.mgr.__exit__, None, None, None)
+ self.mgr.__enter__()
diff --git a/tempest/openstack/common/fixture/mockpatch.py b/tempest/openstack/common/fixture/mockpatch.py
new file mode 100644
index 0000000..cd0d6ca
--- /dev/null
+++ b/tempest/openstack/common/fixture/mockpatch.py
@@ -0,0 +1,51 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2010 United States Government as represented by the
+# Administrator of the National Aeronautics and Space Administration.
+# Copyright 2013 Hewlett-Packard Development Company, L.P.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import fixtures
+import mock
+
+
+class PatchObject(fixtures.Fixture):
+ """Deal with code around mock."""
+
+ def __init__(self, obj, attr, **kwargs):
+ self.obj = obj
+ self.attr = attr
+ self.kwargs = kwargs
+
+ def setUp(self):
+ super(PatchObject, self).setUp()
+ _p = mock.patch.object(self.obj, self.attr, **self.kwargs)
+ self.mock = _p.start()
+ self.addCleanup(_p.stop)
+
+
+class Patch(fixtures.Fixture):
+
+ """Deal with code around mock.patch."""
+
+ def __init__(self, obj, **kwargs):
+ self.obj = obj
+ self.kwargs = kwargs
+
+ def setUp(self):
+ super(Patch, self).setUp()
+ _p = mock.patch(self.obj, **self.kwargs)
+ self.mock = _p.start()
+ self.addCleanup(_p.stop)
diff --git a/tempest/openstack/common/fixture/moxstubout.py b/tempest/openstack/common/fixture/moxstubout.py
new file mode 100644
index 0000000..a0e74fd
--- /dev/null
+++ b/tempest/openstack/common/fixture/moxstubout.py
@@ -0,0 +1,34 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2010 United States Government as represented by the
+# Administrator of the National Aeronautics and Space Administration.
+# Copyright 2013 Hewlett-Packard Development Company, L.P.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import fixtures
+import mox
+
+
+class MoxStubout(fixtures.Fixture):
+ """Deal with code around mox and stubout as a fixture."""
+
+ def setUp(self):
+ super(MoxStubout, self).setUp()
+ # emulate some of the mox stuff, we can't use the metaclass
+ # because it screws with our generators
+ self.mox = mox.Mox()
+ self.stubs = self.mox.stubs
+ self.addCleanup(self.mox.UnsetStubs)
+ self.addCleanup(self.mox.VerifyAll)
diff --git a/tempest/openstack/common/gettextutils.py b/tempest/openstack/common/gettextutils.py
index cbf570a..2939ed9 100644
--- a/tempest/openstack/common/gettextutils.py
+++ b/tempest/openstack/common/gettextutils.py
@@ -60,6 +60,8 @@
if USE_LAZY:
return Message(msg, 'tempest')
else:
+ if six.PY3:
+ return _t.gettext(msg)
return _t.ugettext(msg)
@@ -105,13 +107,17 @@
"""
return Message(msg, domain)
- import __builtin__
- __builtin__.__dict__['_'] = _lazy_gettext
+ from six import moves
+ moves.builtins.__dict__['_'] = _lazy_gettext
else:
localedir = '%s_LOCALEDIR' % domain.upper()
- gettext.install(domain,
- localedir=os.environ.get(localedir),
- unicode=True)
+ if six.PY3:
+ gettext.install(domain,
+ localedir=os.environ.get(localedir))
+ else:
+ gettext.install(domain,
+ localedir=os.environ.get(localedir),
+ unicode=True)
class Message(_userString.UserString, object):
@@ -121,8 +127,8 @@
self._msg = msg
self._left_extra_msg = ''
self._right_extra_msg = ''
+ self._locale = None
self.params = None
- self.locale = None
self.domain = domain
@property
@@ -142,8 +148,13 @@
localedir=localedir,
fallback=True)
+ if six.PY3:
+ ugettext = lang.gettext
+ else:
+ ugettext = lang.ugettext
+
full_msg = (self._left_extra_msg +
- lang.ugettext(self._msg) +
+ ugettext(self._msg) +
self._right_extra_msg)
if self.params is not None:
@@ -151,6 +162,33 @@
return six.text_type(full_msg)
+ @property
+ def locale(self):
+ return self._locale
+
+ @locale.setter
+ def locale(self, value):
+ self._locale = value
+ if not self.params:
+ return
+
+ # This Message object may have been constructed with one or more
+ # Message objects as substitution parameters, given as a single
+ # Message, or a tuple or Map containing some, so when setting the
+ # locale for this Message we need to set it for those Messages too.
+ if isinstance(self.params, Message):
+ self.params.locale = value
+ return
+ if isinstance(self.params, tuple):
+ for param in self.params:
+ if isinstance(param, Message):
+ param.locale = value
+ return
+ if isinstance(self.params, dict):
+ for param in self.params.values():
+ if isinstance(param, Message):
+ param.locale = value
+
def _save_dictionary_parameter(self, dict_param):
full_msg = self.data
# look for %(blah) fields in string;
@@ -169,7 +207,7 @@
params[key] = copy.deepcopy(dict_param[key])
except TypeError:
# cast uncopyable thing to unicode string
- params[key] = unicode(dict_param[key])
+ params[key] = six.text_type(dict_param[key])
return params
@@ -188,7 +226,7 @@
try:
self.params = copy.deepcopy(other)
except TypeError:
- self.params = unicode(other)
+ self.params = six.text_type(other)
return self
@@ -197,11 +235,13 @@
return self.data
def __str__(self):
+ if six.PY3:
+ return self.__unicode__()
return self.data.encode('utf-8')
def __getstate__(self):
to_copy = ['_msg', '_right_extra_msg', '_left_extra_msg',
- 'domain', 'params', 'locale']
+ 'domain', 'params', '_locale']
new_dict = self.__dict__.fromkeys(to_copy)
for attr in to_copy:
new_dict[attr] = copy.deepcopy(self.__dict__[attr])
@@ -289,13 +329,21 @@
def get_localized_message(message, user_locale):
- """Gets a localized version of the given message in the given locale."""
+ """Gets a localized version of the given message in the given locale.
+
+ If the message is not a Message object the message is returned as-is.
+ If the locale is None the message is translated to the default locale.
+
+ :returns: the translated message in unicode, or the original message if
+ it could not be translated
+ """
+ translated = message
if isinstance(message, Message):
- if user_locale:
- message.locale = user_locale
- return unicode(message)
- else:
- return message
+ original_locale = message.locale
+ message.locale = user_locale
+ translated = six.text_type(message)
+ message.locale = original_locale
+ return translated
class LocaleHandler(logging.Handler):
diff --git a/tempest/openstack/common/jsonutils.py b/tempest/openstack/common/jsonutils.py
index c568a06..b589545 100644
--- a/tempest/openstack/common/jsonutils.py
+++ b/tempest/openstack/common/jsonutils.py
@@ -46,6 +46,7 @@
import six
+from tempest.openstack.common import gettextutils
from tempest.openstack.common import importutils
from tempest.openstack.common import timeutils
@@ -135,6 +136,8 @@
if convert_datetime and isinstance(value, datetime.datetime):
return timeutils.strtime(value)
+ elif isinstance(value, gettextutils.Message):
+ return value.data
elif hasattr(value, 'iteritems'):
return recursive(dict(value.iteritems()), level=level + 1)
elif hasattr(value, '__iter__'):
diff --git a/tempest/openstack/common/lockutils.py b/tempest/openstack/common/lockutils.py
index 0abd1a7..8ea8766 100644
--- a/tempest/openstack/common/lockutils.py
+++ b/tempest/openstack/common/lockutils.py
@@ -241,13 +241,14 @@
def wrap(f):
@functools.wraps(f)
def inner(*args, **kwargs):
- with lock(name, lock_file_prefix, external, lock_path):
- LOG.debug(_('Got semaphore / lock "%(function)s"'),
+ try:
+ with lock(name, lock_file_prefix, external, lock_path):
+ LOG.debug(_('Got semaphore / lock "%(function)s"'),
+ {'function': f.__name__})
+ return f(*args, **kwargs)
+ finally:
+ LOG.debug(_('Semaphore / lock released "%(function)s"'),
{'function': f.__name__})
- return f(*args, **kwargs)
-
- LOG.debug(_('Semaphore / lock released "%(function)s"'),
- {'function': f.__name__})
return inner
return wrap
diff --git a/tempest/openstack/common/log.py b/tempest/openstack/common/log.py
index 4133c30..5cf8ed6 100644
--- a/tempest/openstack/common/log.py
+++ b/tempest/openstack/common/log.py
@@ -39,6 +39,7 @@
import traceback
from oslo.config import cfg
+import six
from six import moves
from tempest.openstack.common.gettextutils import _ # noqa
@@ -131,7 +132,6 @@
'boto=WARN',
'suds=INFO',
'keystone=INFO',
- 'eventlet.wsgi.server=WARN'
],
help='list of logger=LEVEL pairs'),
cfg.BoolOpt('publish_errors',
@@ -207,6 +207,8 @@
binary = binary or _get_binary_name()
return '%s.log' % (os.path.join(logdir, binary),)
+ return None
+
class BaseLoggerAdapter(logging.LoggerAdapter):
@@ -249,6 +251,13 @@
self.warn(stdmsg, *args, **kwargs)
def process(self, msg, kwargs):
+ # NOTE(mrodden): catch any Message/other object and
+ # coerce to unicode before they can get
+ # to the python logging and possibly
+ # cause string encoding trouble
+ if not isinstance(msg, six.string_types):
+ msg = six.text_type(msg)
+
if 'extra' not in kwargs:
kwargs['extra'] = {}
extra = kwargs['extra']
@@ -260,14 +269,14 @@
extra.update(_dictify_context(context))
instance = kwargs.pop('instance', None)
+ instance_uuid = (extra.get('instance_uuid', None) or
+ kwargs.pop('instance_uuid', None))
instance_extra = ''
if instance:
instance_extra = CONF.instance_format % instance
- else:
- instance_uuid = kwargs.pop('instance_uuid', None)
- if instance_uuid:
- instance_extra = (CONF.instance_uuid_format
- % {'uuid': instance_uuid})
+ elif instance_uuid:
+ instance_extra = (CONF.instance_uuid_format
+ % {'uuid': instance_uuid})
extra.update({'instance': instance_extra})
extra.update({"project": self.project})
diff --git a/tempest/openstack/common/timeutils.py b/tempest/openstack/common/timeutils.py
index 60f02bc..98d877d 100644
--- a/tempest/openstack/common/timeutils.py
+++ b/tempest/openstack/common/timeutils.py
@@ -117,12 +117,15 @@
utcnow.override_time = None
-def set_time_override(override_time=datetime.datetime.utcnow()):
+def set_time_override(override_time=None):
"""Overrides utils.utcnow.
Make it return a constant time or a list thereof, one at a time.
+
+ :param override_time: datetime instance or list thereof. If not
+ given, defaults to the current UTC time.
"""
- utcnow.override_time = override_time
+ utcnow.override_time = override_time or datetime.datetime.utcnow()
def advance_time_delta(timedelta):
diff --git a/tempest/scenario/README.rst b/tempest/scenario/README.rst
index ce12a3b..835ba99 100644
--- a/tempest/scenario/README.rst
+++ b/tempest/scenario/README.rst
@@ -10,10 +10,13 @@
of a previous part. They ideally involve the integration between
multiple OpenStack services to exercise the touch points between them.
-An example would be: start with a blank environment, upload a glance
-image, deploy a vm from it, ssh to the guest, make changes, capture
-that vm's image back into glance as a snapshot, and launch a second vm
-from that snapshot.
+Any scenario test should have a real-life use case. An example would be:
+
+ - "As operator I want to start with a blank environment":
+ 1. upload a glance image
+ 2. deploy a vm from it
+ 3. ssh to the guest
+ 4. create a snapshot of the vm
Why are these tests in tempest?
diff --git a/tempest/scenario/manager.py b/tempest/scenario/manager.py
index 8ccc899..7848afc 100644
--- a/tempest/scenario/manager.py
+++ b/tempest/scenario/manager.py
@@ -34,7 +34,7 @@
from tempest.api.network import common as net_common
from tempest.common import isolated_creds
from tempest.common import ssh
-from tempest.common.utils.data_utils import rand_name
+from tempest.common.utils import data_utils
from tempest.common.utils.linux.remote_client import RemoteClient
from tempest import exceptions
import tempest.manager
@@ -88,6 +88,7 @@
auth_url = self.config.identity.uri
dscv = self.config.identity.disable_ssl_certificate_validation
+ region = self.config.identity.region
client_args = (username, password, tenant_name, auth_url)
@@ -96,13 +97,16 @@
return novaclient.client.Client(self.NOVACLIENT_VERSION,
*client_args,
service_type=service_type,
+ region_name=region,
no_cache=True,
insecure=dscv,
http_log_debug=True)
def _get_image_client(self):
token = self.identity_client.auth_token
+ region = self.config.identity.region
endpoint = self.identity_client.service_catalog.url_for(
+ attr='region', filter_value=region,
service_type='image', endpoint_type='publicURL')
dscv = self.config.identity.disable_ssl_certificate_validation
return glanceclient.Client('1', endpoint=endpoint, token=token,
@@ -110,11 +114,13 @@
def _get_volume_client(self, username, password, tenant_name):
auth_url = self.config.identity.uri
+ region = self.config.identity.region
return cinderclient.client.Client(self.CINDERCLIENT_VERSION,
username,
password,
tenant_name,
auth_url,
+ region_name=region,
http_log_debug=True)
def _get_orchestration_client(self, username=None, password=None,
@@ -129,9 +135,12 @@
self._validate_credentials(username, password, tenant_name)
keystone = self._get_identity_client(username, password, tenant_name)
+ region = self.config.identity.region
token = keystone.auth_token
try:
endpoint = keystone.service_catalog.url_for(
+ attr='region',
+ filter_value=region,
service_type='orchestration',
endpoint_type='publicURL')
except keystoneclient.exceptions.EndpointNotFound:
@@ -390,7 +399,7 @@
if client is None:
client = self.compute_client
if name is None:
- name = rand_name('scenario-server-')
+ name = data_utils.rand_name('scenario-server-')
if image is None:
image = self.config.compute.image_ref
if flavor is None:
@@ -414,7 +423,7 @@
if client is None:
client = self.volume_client
if name is None:
- name = rand_name('scenario-volume-')
+ name = data_utils.rand_name('scenario-volume-')
LOG.debug("Creating a volume (size: %s, name: %s)", size, name)
volume = client.volumes.create(size=size, display_name=name,
snapshot_id=snapshot_id,
@@ -432,7 +441,7 @@
if image_client is None:
image_client = self.image_client
if name is None:
- name = rand_name('scenario-snapshot-')
+ name = data_utils.rand_name('scenario-snapshot-')
LOG.debug("Creating a snapshot image for server: %s", server.name)
image_id = compute_client.servers.create_image(server, name)
self.addCleanup(image_client.images.delete, image_id)
@@ -447,7 +456,7 @@
if client is None:
client = self.compute_client
if name is None:
- name = rand_name('scenario-keypair-')
+ name = data_utils.rand_name('scenario-keypair-')
keypair = client.keypairs.create(name)
self.assertEqual(keypair.name, name)
self.set_resource(name, keypair)
@@ -501,7 +510,7 @@
if client is None:
client = self.compute_client
# Create security group
- sg_name = rand_name(namestart)
+ sg_name = data_utils.rand_name(namestart)
sg_desc = sg_name + " description"
secgroup = client.security_groups.create(sg_name, sg_desc)
self.assertEqual(secgroup.name, sg_name)
@@ -514,7 +523,7 @@
return secgroup
def _create_network(self, tenant_id, namestart='network-smoke-'):
- name = rand_name(namestart)
+ name = data_utils.rand_name(namestart)
body = dict(
network=dict(
name=name,
@@ -570,11 +579,11 @@
subnet = net_common.DeletableSubnet(client=self.network_client,
**result['subnet'])
self.assertEqual(subnet.cidr, str(subnet_cidr))
- self.set_resource(rand_name(namestart), subnet)
+ self.set_resource(data_utils.rand_name(namestart), subnet)
return subnet
def _create_port(self, network, namestart='port-quotatest-'):
- name = rand_name(namestart)
+ name = data_utils.rand_name(namestart)
body = dict(
port=dict(name=name,
network_id=network.id,
@@ -603,7 +612,7 @@
floating_ip = net_common.DeletableFloatingIp(
client=self.network_client,
**result['floatingip'])
- self.set_resource(rand_name('floatingip-'), floating_ip)
+ self.set_resource(data_utils.rand_name('floatingip-'), floating_ip)
return floating_ip
def _ping_ip_address(self, ip_address):
@@ -666,7 +675,7 @@
@classmethod
def _stack_rand_name(cls):
- return rand_name(cls.__name__ + '-')
+ return data_utils.rand_name(cls.__name__ + '-')
@classmethod
def _get_default_network(cls):
diff --git a/tempest/scenario/orchestration/test_autoscaling.py b/tempest/scenario/orchestration/test_autoscaling.py
index 658e9bb..e843793 100644
--- a/tempest/scenario/orchestration/test_autoscaling.py
+++ b/tempest/scenario/orchestration/test_autoscaling.py
@@ -12,6 +12,7 @@
# License for the specific language governing permissions and limitations
# under the License.
+import heatclient.exc as heat_exceptions
import time
from tempest.scenario import manager
@@ -74,7 +75,8 @@
self.assertEqual('CREATE', self.stack.action)
# wait for create to complete.
- self.status_timeout(self.client.stacks, sid, 'COMPLETE')
+ self.status_timeout(self.client.stacks, sid, 'COMPLETE',
+ error_status='FAILED')
self.stack.get()
self.assertEqual('CREATE_COMPLETE', self.stack.stack_status)
@@ -96,8 +98,10 @@
call_until_true(lambda: server_count() == to_servers,
timeout, interval)
self.assertEqual(to_servers, self.server_count,
- 'Failed scaling from %d to %d servers' % (
- from_servers, to_servers))
+ 'Failed scaling from %d to %d servers. '
+ 'Current server count: %s' % (
+ from_servers, to_servers,
+ self.server_count))
# he marched them up to the top of the hill
assertScale(1, 2)
@@ -106,3 +110,15 @@
# and he marched them down again
assertScale(3, 2)
assertScale(2, 1)
+
+ # delete stack on completion
+ self.stack.delete()
+ self.status_timeout(self.client.stacks, sid, 'COMPLETE',
+ error_status='FAILED',
+ not_found_exception=heat_exceptions.NotFound)
+
+ try:
+ self.stack.get()
+ self.assertEqual('DELETE_COMPLETE', self.stack.stack_status)
+ except heat_exceptions.NotFound:
+ pass
diff --git a/tempest/scenario/test_large_ops.py b/tempest/scenario/test_large_ops.py
index 33b7adc..30c223f 100644
--- a/tempest/scenario/test_large_ops.py
+++ b/tempest/scenario/test_large_ops.py
@@ -15,7 +15,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest.common.utils.data_utils import rand_name
+from tempest.common.utils import data_utils
from tempest.openstack.common import log as logging
from tempest.scenario import manager
from tempest.test import services
@@ -24,7 +24,7 @@
LOG = logging.getLogger(__name__)
-class TestLargeOpsScenario(manager.OfficialClientTest):
+class TestLargeOpsScenario(manager.NetworkScenarioTest):
"""
Test large operations.
@@ -47,7 +47,7 @@
self.volume_client.volumes, volume_id, status)
def _image_create(self, name, fmt, path, properties={}):
- name = rand_name('%s-' % name)
+ name = data_utils.rand_name('%s-' % name)
image_file = open(path, 'rb')
self.addCleanup(image_file.close)
params = {
@@ -82,19 +82,19 @@
properties=properties)
def nova_boot(self):
- def delete(servers):
- [x.delete() for x in servers]
-
- name = rand_name('scenario-server-')
+ name = data_utils.rand_name('scenario-server-')
client = self.compute_client
flavor_id = self.config.compute.flavor_ref
+ secgroup = self._create_security_group()
self.servers = client.servers.create(
name=name, image=self.image,
flavor=flavor_id,
- min_count=self.config.scenario.large_ops_number)
+ min_count=self.config.scenario.large_ops_number,
+ security_groups=[secgroup.name])
# needed because of bug 1199788
self.servers = [x for x in client.servers.list() if name in x.name]
- self.addCleanup(delete, self.servers)
+ for server in self.servers:
+ self.set_resource(server.name, server)
self._wait_for_server_status('ACTIVE')
@services('compute', 'image')
diff --git a/tempest/scenario/test_minimum_basic.py b/tempest/scenario/test_minimum_basic.py
index 752ff6f..9cc8541 100644
--- a/tempest/scenario/test_minimum_basic.py
+++ b/tempest/scenario/test_minimum_basic.py
@@ -15,7 +15,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest.common.utils.data_utils import rand_name
+from tempest.common.utils import data_utils
from tempest.openstack.common import log as logging
from tempest.scenario import manager
from tempest.test import services
@@ -48,7 +48,7 @@
self.volume_client.volumes, volume_id, status)
def _image_create(self, name, fmt, path, properties={}):
- name = rand_name('%s-' % name)
+ name = data_utils.rand_name('%s-' % name)
image_file = open(path, 'rb')
self.addCleanup(image_file.close)
params = {
diff --git a/tempest/scenario/test_network_basic_ops.py b/tempest/scenario/test_network_basic_ops.py
index 6cd9fe8..a7618b1 100644
--- a/tempest/scenario/test_network_basic_ops.py
+++ b/tempest/scenario/test_network_basic_ops.py
@@ -18,7 +18,7 @@
from tempest.api.network import common as net_common
from tempest.common import debug
-from tempest.common.utils.data_utils import rand_name
+from tempest.common.utils import data_utils
from tempest import config
from tempest.openstack.common import log as logging
from tempest.scenario import manager
@@ -144,7 +144,7 @@
"'public_network_id' has been defined.")
def _create_router(self, tenant_id, namestart='router-smoke-'):
- name = rand_name(namestart)
+ name = data_utils.rand_name(namestart)
body = dict(
router=dict(
name=name,
@@ -161,7 +161,7 @@
def _create_keypairs(self):
self.keypairs[self.tenant_id] = self.create_keypair(
- name=rand_name('keypair-smoke-'))
+ name=data_utils.rand_name('keypair-smoke-'))
def _create_security_groups(self):
self.security_groups[self.tenant_id] = self._create_security_group()
@@ -214,7 +214,7 @@
def _create_servers(self):
for i, network in enumerate(self.networks):
- name = rand_name('server-smoke-%d-' % i)
+ name = data_utils.rand_name('server-smoke-%d-' % i)
server = self._create_server(name, network)
self.servers.append(server)
diff --git a/tempest/scenario/test_server_advanced_ops.py b/tempest/scenario/test_server_advanced_ops.py
index 853b1ba..112c8a2 100644
--- a/tempest/scenario/test_server_advanced_ops.py
+++ b/tempest/scenario/test_server_advanced_ops.py
@@ -35,7 +35,7 @@
def setUpClass(cls):
super(TestServerAdvancedOps, cls).setUpClass()
- if not cls.config.compute.resize_available:
+ if not cls.config.compute_feature_enabled.resize:
msg = "Skipping test - resize not available on this host"
raise cls.skipException(msg)
diff --git a/tempest/scenario/test_server_basic_ops.py b/tempest/scenario/test_server_basic_ops.py
index c32d49d..0b08f9c 100644
--- a/tempest/scenario/test_server_basic_ops.py
+++ b/tempest/scenario/test_server_basic_ops.py
@@ -15,7 +15,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest.common.utils.data_utils import rand_name
+from tempest.common.utils import data_utils
from tempest.openstack.common import log as logging
from tempest.scenario import manager
from tempest.test import services
@@ -41,7 +41,7 @@
self.keypair = self.create_keypair()
def create_security_group(self):
- sg_name = rand_name('secgroup-smoke')
+ sg_name = data_utils.rand_name('secgroup-smoke')
sg_desc = sg_name + " description"
self.secgroup = self.compute_client.security_groups.create(sg_name,
sg_desc)
diff --git a/tempest/scenario/test_stamp_pattern.py b/tempest/scenario/test_stamp_pattern.py
index 4f49d65..a8cedc7 100644
--- a/tempest/scenario/test_stamp_pattern.py
+++ b/tempest/scenario/test_stamp_pattern.py
@@ -18,9 +18,8 @@
import time
from cinderclient import exceptions as cinder_exceptions
-import testtools
-from tempest.common.utils.data_utils import rand_name
+from tempest.common.utils import data_utils
from tempest import exceptions
from tempest.openstack.common import log as logging
from tempest.scenario import manager
@@ -76,7 +75,7 @@
return linux_client.ssh_client
def _create_volume_snapshot(self, volume):
- snapshot_name = rand_name('scenario-snapshot-')
+ snapshot_name = data_utils.rand_name('scenario-snapshot-')
volume_snapshots = self.volume_client.volume_snapshots
snapshot = volume_snapshots.create(
volume.id, display_name=snapshot_name)
@@ -142,7 +141,7 @@
got_timestamp = ssh_client.exec_command('sudo cat /mnt/timestamp')
self.assertEqual(self.timestamp, got_timestamp)
- @testtools.skip("Skipped until the Bug #1205344 is resolved.")
+ @tempest.test.skip_because(bug="1205344")
@tempest.test.services('compute', 'network', 'volume', 'image')
def test_stamp_pattern(self):
# prepare for booting a instance
diff --git a/tempest/scenario/test_volume_boot_pattern.py b/tempest/scenario/test_volume_boot_pattern.py
index d12cd56..84846c1 100644
--- a/tempest/scenario/test_volume_boot_pattern.py
+++ b/tempest/scenario/test_volume_boot_pattern.py
@@ -12,7 +12,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest.common.utils.data_utils import rand_name
+from tempest.common.utils import data_utils
from tempest.scenario import manager
from tempest.test import services
@@ -34,7 +34,7 @@
def _create_volume_from_image(self):
img_uuid = self.config.compute.image_ref
- vol_name = rand_name('volume-origin')
+ vol_name = data_utils.rand_name('volume-origin')
return self.create_volume(name=vol_name, imageRef=img_uuid)
def _boot_instance_from_volume(self, vol_id, keypair):
@@ -53,7 +53,7 @@
def _create_snapshot_from_volume(self, vol_id):
volume_snapshots = self.volume_client.volume_snapshots
- snap_name = rand_name('snapshot')
+ snap_name = data_utils.rand_name('snapshot')
snap = volume_snapshots.create(volume_id=vol_id,
force=True,
display_name=snap_name)
@@ -64,7 +64,7 @@
return snap
def _create_volume_from_snapshot(self, snap_id):
- vol_name = rand_name('volume')
+ vol_name = data_utils.rand_name('volume')
return self.create_volume(name=vol_name, snapshot_id=snap_id)
def _stop_instances(self, instances):
@@ -88,7 +88,7 @@
def _ssh_to_server(self, server, keypair):
if self.config.compute.use_floatingip_for_ssh:
floating_ip = self.compute_client.floating_ips.create()
- fip_name = rand_name('scenario-fip')
+ fip_name = data_utils.rand_name('scenario-fip')
self.set_resource(fip_name, floating_ip)
server.add_floating_ip(floating_ip)
ip = floating_ip.ip
@@ -104,7 +104,7 @@
return ssh_client.exec_command('cat /tmp/text')
def _write_text(self, ssh_client):
- text = rand_name('text-')
+ text = data_utils.rand_name('text-')
ssh_client.exec_command('echo "%s" > /tmp/text; sync' % (text))
return self._get_content(ssh_client)
diff --git a/tempest/services/compute/json/aggregates_client.py b/tempest/services/compute/json/aggregates_client.py
index 75ce9ff..b7c6bf1 100644
--- a/tempest/services/compute/json/aggregates_client.py
+++ b/tempest/services/compute/json/aggregates_client.py
@@ -97,3 +97,14 @@
post_body, self.headers)
body = json.loads(body)
return resp, body['aggregate']
+
+ def set_metadata(self, aggregate_id, meta):
+ """Replaces the aggregate's existing metadata with new metadata."""
+ post_body = {
+ 'metadata': meta,
+ }
+ post_body = json.dumps({'set_metadata': post_body})
+ resp, body = self.post('os-aggregates/%s/action' % aggregate_id,
+ post_body, self.headers)
+ body = json.loads(body)
+ return resp, body['aggregate']
diff --git a/tempest/services/compute/json/certificates_client.py b/tempest/services/compute/json/certificates_client.py
new file mode 100644
index 0000000..9fdce17
--- /dev/null
+++ b/tempest/services/compute/json/certificates_client.py
@@ -0,0 +1,42 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 IBM Corp
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import json
+
+from tempest.common.rest_client import RestClient
+
+
+class CertificatesClientJSON(RestClient):
+
+ def __init__(self, config, username, password, auth_url, tenant_name=None):
+ super(CertificatesClientJSON, self).__init__(config, username,
+ password,
+ auth_url, tenant_name)
+ self.service = self.config.compute.catalog_type
+
+ def get_certificate(self, id):
+ url = "os-certificates/%s" % (id)
+ resp, body = self.get(url)
+ body = json.loads(body)
+ return resp, body['certificate']
+
+ def create_certificate(self):
+ """create certificates."""
+ url = "os-certificates"
+ resp, body = self.post(url, None, self.headers)
+ body = json.loads(body)
+ return resp, body['certificate']
diff --git a/tempest/services/compute/json/flavors_client.py b/tempest/services/compute/json/flavors_client.py
index c3b568d..00d6f8a 100644
--- a/tempest/services/compute/json/flavors_client.py
+++ b/tempest/services/compute/json/flavors_client.py
@@ -102,11 +102,33 @@
body = json.loads(body)
return resp, body['extra_specs']
+ def get_flavor_extra_spec_with_key(self, flavor_id, key):
+ """Gets extra Specs key-value of the mentioned flavor and key."""
+ resp, body = self.get('flavors/%s/os-extra_specs/%s' % (str(flavor_id),
+ key))
+ body = json.loads(body)
+ return resp, body
+
+ def update_flavor_extra_spec(self, flavor_id, key, **kwargs):
+ """Update specified extra Specs of the mentioned flavor and key."""
+ resp, body = self.put('flavors/%s/os-extra_specs/%s' %
+ (flavor_id, key),
+ json.dumps(kwargs), self.headers)
+ body = json.loads(body)
+ return resp, body
+
def unset_flavor_extra_spec(self, flavor_id, key):
"""Unsets extra Specs from the mentioned flavor."""
return self.delete('flavors/%s/os-extra_specs/%s' % (str(flavor_id),
key))
+ def list_flavor_access(self, flavor_id):
+ """Gets flavor access information given the flavor id."""
+ resp, body = self.get('flavors/%s/os-flavor-access' % flavor_id,
+ self.headers)
+ body = json.loads(body)
+ return resp, body['flavor_access']
+
def add_flavor_access(self, flavor_id, tenant_id):
"""Add flavor access for the specified tenant."""
post_body = {
diff --git a/tempest/services/compute/json/hosts_client.py b/tempest/services/compute/json/hosts_client.py
index 30a3f7b..f51879d 100644
--- a/tempest/services/compute/json/hosts_client.py
+++ b/tempest/services/compute/json/hosts_client.py
@@ -44,3 +44,39 @@
resp, body = self.get("os-hosts/%s" % str(hostname))
body = json.loads(body)
return resp, body['host']
+
+ def update_host(self, hostname, **kwargs):
+ """Update a host."""
+
+ request_body = {
+ 'status': None,
+ 'maintenance_mode': None,
+ }
+ request_body.update(**kwargs)
+ request_body = json.dumps(request_body)
+
+ resp, body = self.put("os-hosts/%s" % str(hostname), request_body,
+ self.headers)
+ body = json.loads(body)
+ return resp, body
+
+ def startup_host(self, hostname):
+ """Startup a host."""
+
+ resp, body = self.get("os-hosts/%s/startup" % str(hostname))
+ body = json.loads(body)
+ return resp, body['host']
+
+ def shutdown_host(self, hostname):
+ """Shutdown a host."""
+
+ resp, body = self.get("os-hosts/%s/shutdown" % str(hostname))
+ body = json.loads(body)
+ return resp, body['host']
+
+ def reboot_host(self, hostname):
+ """reboot a host."""
+
+ resp, body = self.get("os-hosts/%s/reboot" % str(hostname))
+ body = json.loads(body)
+ return resp, body['host']
diff --git a/tempest/services/compute/json/hypervisor_client.py b/tempest/services/compute/json/hypervisor_client.py
index e2e5c7b..fba5acb 100644
--- a/tempest/services/compute/json/hypervisor_client.py
+++ b/tempest/services/compute/json/hypervisor_client.py
@@ -63,3 +63,9 @@
resp, body = self.get('os-hypervisors/%s/uptime' % hyper_id)
body = json.loads(body)
return resp, body['hypervisor']
+
+ def search_hypervisor(self, hyper_name):
+ """Search specified hypervisor."""
+ resp, body = self.get('os-hypervisors/%s/search' % hyper_name)
+ body = json.loads(body)
+ return resp, body['hypervisors']
diff --git a/tempest/services/compute/json/instance_usage_audit_log_client.py b/tempest/services/compute/json/instance_usage_audit_log_client.py
new file mode 100644
index 0000000..07ce1bb
--- /dev/null
+++ b/tempest/services/compute/json/instance_usage_audit_log_client.py
@@ -0,0 +1,40 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 IBM Corporation
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import json
+
+from tempest.common.rest_client import RestClient
+
+
+class InstanceUsagesAuditLogClientJSON(RestClient):
+
+ def __init__(self, config, username, password, auth_url, tenant_name=None):
+ super(InstanceUsagesAuditLogClientJSON, self).__init__(
+ config, username, password, auth_url, tenant_name)
+ self.service = self.config.compute.catalog_type
+
+ def list_instance_usage_audit_logs(self):
+ url = 'os-instance_usage_audit_log'
+ resp, body = self.get(url)
+ body = json.loads(body)
+ return resp, body["instance_usage_audit_logs"]
+
+ def get_instance_usage_audit_log(self, time_before):
+ url = 'os-instance_usage_audit_log/%s' % time_before
+ resp, body = self.get(url)
+ body = json.loads(body)
+ return resp, body["instance_usage_audit_log"]
diff --git a/tempest/services/compute/json/servers_client.py b/tempest/services/compute/json/servers_client.py
index eda0ede..3c6a40f 100644
--- a/tempest/services/compute/json/servers_client.py
+++ b/tempest/services/compute/json/servers_client.py
@@ -89,7 +89,7 @@
return resp, body['server']
def update_server(self, server_id, name=None, meta=None, accessIPv4=None,
- accessIPv6=None):
+ accessIPv6=None, disk_config=None):
"""
Updates the properties of an existing server.
server_id: The id of an existing server.
@@ -113,6 +113,9 @@
if accessIPv6 is not None:
post_body['accessIPv6'] = accessIPv6
+ if disk_config is not None:
+ post_body['OS-DCF:diskConfig'] = disk_config
+
post_body = json.dumps({'server': post_body})
resp, body = self.put("servers/%s" % str(server_id),
post_body, self.headers)
@@ -151,9 +154,10 @@
body = json.loads(body)
return resp, body
- def wait_for_server_status(self, server_id, status):
+ def wait_for_server_status(self, server_id, status, extra_timeout=0):
"""Waits for a server to reach a given status."""
- return waiters.wait_for_server_status(self, server_id, status)
+ return waiters.wait_for_server_status(self, server_id, status,
+ extra_timeout=extra_timeout)
def wait_for_server_termination(self, server_id, ignore_error=False):
"""Waits for server to reach termination."""
@@ -194,11 +198,33 @@
body = json.loads(body)[response_key]
return resp, body
+ def create_backup(self, server_id, backup_type, rotation, name):
+ """Backup a server instance."""
+ return self.action(server_id, "createBackup", None,
+ backup_type=backup_type,
+ rotation=rotation,
+ name=name)
+
def change_password(self, server_id, adminPass):
"""Changes the root password for the server."""
return self.action(server_id, 'changePassword', None,
adminPass=adminPass)
+ def get_password(self, server_id):
+ resp, body = self.get("servers/%s/os-server-password" %
+ str(server_id))
+ body = json.loads(body)
+ return resp, body
+
+ def delete_password(self, server_id):
+ """
+ Removes the encrypted server password from the metadata server
+ Note that this does not actually change the instance server
+ password.
+ """
+ return self.delete("servers/%s/os-server-password" %
+ str(server_id))
+
def reboot(self, server_id, reboot_type):
"""Reboots a server."""
return self.action(server_id, 'reboot', None, type=reboot_type)
@@ -236,8 +262,11 @@
body = json.loads(body)
return resp, body['metadata']
- def set_server_metadata(self, server_id, meta):
- post_body = json.dumps({'metadata': meta})
+ def set_server_metadata(self, server_id, meta, no_metadata_field=False):
+ if no_metadata_field:
+ post_body = ""
+ else:
+ post_body = json.dumps({'metadata': meta})
resp, body = self.put('servers/%s/metadata' % str(server_id),
post_body, self.headers)
body = json.loads(body)
@@ -327,25 +356,33 @@
return self.action(server_id, 'unlock', None, **kwargs)
def suspend_server(self, server_id, **kwargs):
- """Suspends the provded server."""
+ """Suspends the provided server."""
return self.action(server_id, 'suspend', None, **kwargs)
def resume_server(self, server_id, **kwargs):
- """Un-suspends the provded server."""
+ """Un-suspends the provided server."""
return self.action(server_id, 'resume', None, **kwargs)
def pause_server(self, server_id, **kwargs):
- """Pauses the provded server."""
+ """Pauses the provided server."""
return self.action(server_id, 'pause', None, **kwargs)
def unpause_server(self, server_id, **kwargs):
- """Un-pauses the provded server."""
+ """Un-pauses the provided server."""
return self.action(server_id, 'unpause', None, **kwargs)
def reset_state(self, server_id, state='error'):
"""Resets the state of a server to active/error."""
return self.action(server_id, 'os-resetState', None, state=state)
+ def shelve_server(self, server_id, **kwargs):
+ """Shelves the provided server."""
+ return self.action(server_id, 'shelve', None, **kwargs)
+
+ def unshelve_server(self, server_id, **kwargs):
+ """Un-shelves the provided server."""
+ return self.action(server_id, 'unshelve', None, **kwargs)
+
def get_console_output(self, server_id, length):
return self.action(server_id, 'os-getConsoleOutput', 'output',
length=length)
@@ -384,3 +421,11 @@
(str(server_id), str(request_id)))
body = json.loads(body)
return resp, body['instanceAction']
+
+ def force_delete_server(self, server_id, **kwargs):
+ """Force delete a server."""
+ return self.action(server_id, 'forceDelete', None, **kwargs)
+
+ def restore_soft_deleted_server(self, server_id, **kwargs):
+ """Restore a soft-deleted server."""
+ return self.action(server_id, 'restore', None, **kwargs)
diff --git a/tempest/services/compute/v3/__init__.py b/tempest/services/compute/v3/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/services/compute/v3/__init__.py
diff --git a/tempest/services/compute/v3/json/__init__.py b/tempest/services/compute/v3/json/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/services/compute/v3/json/__init__.py
diff --git a/tempest/services/compute/v3/json/availability_zone_client.py b/tempest/services/compute/v3/json/availability_zone_client.py
new file mode 100644
index 0000000..9a3fe8b
--- /dev/null
+++ b/tempest/services/compute/v3/json/availability_zone_client.py
@@ -0,0 +1,39 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 NEC Corporation.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import json
+
+from tempest.common.rest_client import RestClient
+
+
+class AvailabilityZoneV3ClientJSON(RestClient):
+
+ def __init__(self, config, username, password, auth_url, tenant_name=None):
+ super(AvailabilityZoneV3ClientJSON, self).__init__(config, username,
+ password, auth_url,
+ tenant_name)
+ self.service = self.config.compute.catalog_v3_type
+
+ def get_availability_zone_list(self):
+ resp, body = self.get('os-availability-zone')
+ body = json.loads(body)
+ return resp, body['availability_zone_info']
+
+ def get_availability_zone_list_detail(self):
+ resp, body = self.get('os-availability-zone/detail')
+ body = json.loads(body)
+ return resp, body['availability_zone_info']
diff --git a/tempest/services/compute/v3/json/extensions_client.py b/tempest/services/compute/v3/json/extensions_client.py
new file mode 100644
index 0000000..60c0217
--- /dev/null
+++ b/tempest/services/compute/v3/json/extensions_client.py
@@ -0,0 +1,40 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 OpenStack Foundation
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import json
+
+from tempest.common.rest_client import RestClient
+
+
+class ExtensionsV3ClientJSON(RestClient):
+
+ def __init__(self, config, username, password, auth_url, tenant_name=None):
+ super(ExtensionsV3ClientJSON, self).__init__(config, username,
+ password, auth_url,
+ tenant_name)
+ self.service = self.config.compute.catalog_v3_type
+
+ def list_extensions(self):
+ url = 'extensions'
+ resp, body = self.get(url)
+ body = json.loads(body)
+ return resp, body
+
+ def is_enabled(self, extension):
+ _, extensions = self.list_extensions()
+ exts = extensions['extensions']
+ return any([e for e in exts if e['name'] == extension])
diff --git a/tempest/services/compute/v3/json/interfaces_client.py b/tempest/services/compute/v3/json/interfaces_client.py
new file mode 100644
index 0000000..7fb0fa9
--- /dev/null
+++ b/tempest/services/compute/v3/json/interfaces_client.py
@@ -0,0 +1,83 @@
+# Copyright 2013 IBM Corp.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import json
+import time
+
+from tempest.common.rest_client import RestClient
+from tempest import exceptions
+
+
+class InterfacesV3ClientJSON(RestClient):
+
+ def __init__(self, config, username, password, auth_url, tenant_name=None):
+ super(InterfacesV3ClientJSON, self).__init__(config, username,
+ password, auth_url,
+ tenant_name)
+ self.service = self.config.compute.catalog_v3_type
+
+ def list_interfaces(self, server):
+ resp, body = self.get('servers/%s/os-attach-interfaces' % server)
+ body = json.loads(body)
+ return resp, body['interface_attachments']
+
+ def create_interface(self, server, port_id=None, network_id=None,
+ fixed_ip=None):
+ post_body = dict(interface_attachment=dict())
+ if port_id:
+ post_body['port_id'] = port_id
+ if network_id:
+ post_body['net_id'] = network_id
+ if fixed_ip:
+ post_body['fixed_ips'] = [dict(ip_address=fixed_ip)]
+ post_body = json.dumps(post_body)
+ resp, body = self.post('servers/%s/os-attach-interfaces' % server,
+ headers=self.headers,
+ body=post_body)
+ body = json.loads(body)
+ return resp, body['interface_attachment']
+
+ def show_interface(self, server, port_id):
+ resp, body =\
+ self.get('servers/%s/os-attach-interfaces/%s' % (server, port_id))
+ body = json.loads(body)
+ return resp, body['interface_attachment']
+
+ def delete_interface(self, server, port_id):
+ resp, body =\
+ self.delete('servers/%s/os-attach-interfaces/%s' % (server,
+ port_id))
+ return resp, body
+
+ def wait_for_interface_status(self, server, port_id, status):
+ """Waits for a interface to reach a given status."""
+ resp, body = self.show_interface(server, port_id)
+ interface_status = body['port_state']
+ start = int(time.time())
+
+ while(interface_status != status):
+ time.sleep(self.build_interval)
+ resp, body = self.show_interface(server, port_id)
+ interface_status = body['port_state']
+
+ timed_out = int(time.time()) - start >= self.build_timeout
+
+ if interface_status != status and timed_out:
+ message = ('Interface %s failed to reach %s status within '
+ 'the required time (%s s).' %
+ (port_id, status, self.build_timeout))
+ raise exceptions.TimeoutException(message)
+
+ return resp, body
diff --git a/tempest/services/compute/v3/json/servers_client.py b/tempest/services/compute/v3/json/servers_client.py
new file mode 100644
index 0000000..cddbb53
--- /dev/null
+++ b/tempest/services/compute/v3/json/servers_client.py
@@ -0,0 +1,398 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 OpenStack Foundation
+# Copyright 2013 Hewlett-Packard Development Company, L.P.
+# Copyright 2013 IBM Corp
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import json
+import time
+import urllib
+
+from tempest.common.rest_client import RestClient
+from tempest.common import waiters
+from tempest import exceptions
+
+
+class ServersV3ClientJSON(RestClient):
+
+ def __init__(self, config, username, password, auth_url,
+ tenant_name=None, auth_version='v2'):
+ super(ServersV3ClientJSON, self).__init__(config, username, password,
+ auth_url, tenant_name,
+ auth_version=auth_version)
+ self.service = self.config.compute.catalog_v3_type
+
+ def create_server(self, name, image_ref, flavor_ref, **kwargs):
+ """
+ Creates an instance of a server.
+ name (Required): The name of the server.
+ image_ref (Required): Reference to the image used to build the server.
+ flavor_ref (Required): The flavor used to build the server.
+ Following optional keyword arguments are accepted:
+ admin_password: Sets the initial root password.
+ key_name: Key name of keypair that was created earlier.
+ meta: A dictionary of values to be used as metadata.
+ personality: A list of dictionaries for files to be injected into
+ the server.
+ security_groups: A list of security group dicts.
+ networks: A list of network dicts with UUID and fixed_ip.
+ user_data: User data for instance.
+ availability_zone: Availability zone in which to launch instance.
+ access_ip_v4: The IPv4 access address for the server.
+ access_ip_v6: The IPv6 access address for the server.
+ min_count: Count of minimum number of instances to launch.
+ max_count: Count of maximum number of instances to launch.
+ disk_config: Determines if user or admin controls disk configuration.
+ return_reservation_id: Enable/Disable the return of reservation id
+ """
+ post_body = {
+ 'name': name,
+ 'image_ref': image_ref,
+ 'flavor_ref': flavor_ref
+ }
+
+ for option in ['personality', 'admin_password', 'key_name',
+ 'security_groups', 'networks',
+ ('os-user-data:user_data', 'user_data'),
+ ('os-availability-zone:availability_zone',
+ 'availability_zone'),
+ 'access_ip_v4', 'access_ip_v6',
+ ('os-multiple-create:min_count', 'min_count'),
+ ('os-multiple-create:max_count', 'max_count'),
+ ('metadata', 'meta'),
+ ('os-disk-config:disk_config', 'disk_config'),
+ ('os-multiple-create:return_reservation_id',
+ 'return_reservation_id')]:
+ if isinstance(option, tuple):
+ post_param = option[0]
+ key = option[1]
+ else:
+ post_param = option
+ key = option
+ value = kwargs.get(key)
+ if value is not None:
+ post_body[post_param] = value
+ post_body = json.dumps({'server': post_body})
+ resp, body = self.post('servers', post_body, self.headers)
+
+ body = json.loads(body)
+ # NOTE(maurosr): this deals with the case of multiple server create
+ # with return reservation id set True
+ if 'reservation_id' in body:
+ return resp, body
+ return resp, body['server']
+
+ def update_server(self, server_id, name=None, meta=None, access_ip_v4=None,
+ access_ip_v6=None, disk_config=None):
+ """
+ Updates the properties of an existing server.
+ server_id: The id of an existing server.
+ name: The name of the server.
+ personality: A list of files to be injected into the server.
+ access_ip_v4: The IPv4 access address for the server.
+ access_ip_v6: The IPv6 access address for the server.
+ """
+
+ post_body = {}
+
+ if meta is not None:
+ post_body['metadata'] = meta
+
+ if name is not None:
+ post_body['name'] = name
+
+ if access_ip_v4 is not None:
+ post_body['access_ip_v4'] = access_ip_v4
+
+ if access_ip_v6 is not None:
+ post_body['access_ip_v6'] = access_ip_v6
+
+ if disk_config is not None:
+ post_body['OS-DCF:diskConfig'] = disk_config
+
+ post_body = json.dumps({'server': post_body})
+ resp, body = self.put("servers/%s" % str(server_id),
+ post_body, self.headers)
+ body = json.loads(body)
+ return resp, body['server']
+
+ def get_server(self, server_id):
+ """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."""
+ return self.delete("servers/%s" % str(server_id))
+
+ def list_servers(self, params=None):
+ """Lists all servers for a user."""
+
+ url = 'servers'
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+
+ resp, body = self.get(url)
+ body = json.loads(body)
+ return resp, body
+
+ def list_servers_with_detail(self, params=None):
+ """Lists all servers in detail for a user."""
+
+ url = 'servers/detail'
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+
+ resp, body = self.get(url)
+ body = json.loads(body)
+ return resp, body
+
+ def wait_for_server_status(self, server_id, status, extra_timeout=0):
+ """Waits for a server to reach a given status."""
+ return waiters.wait_for_server_status(self, server_id, status,
+ extra_timeout=extra_timeout)
+
+ def wait_for_server_termination(self, server_id, ignore_error=False):
+ """Waits for server to reach termination."""
+ start_time = int(time.time())
+ while True:
+ try:
+ resp, body = self.get_server(server_id)
+ except exceptions.NotFound:
+ return
+
+ server_status = body['status']
+ if server_status == 'ERROR' and not ignore_error:
+ raise exceptions.BuildErrorException(server_id=server_id)
+
+ if int(time.time()) - start_time >= self.build_timeout:
+ raise exceptions.TimeoutException
+
+ time.sleep(self.build_interval)
+
+ def list_addresses(self, server_id):
+ """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."""
+ resp, body = self.get("servers/%s/ips/%s" %
+ (str(server_id), network_id))
+ body = json.loads(body)
+ return resp, body
+
+ def action(self, server_id, action_name, response_key, **kwargs):
+ post_body = json.dumps({action_name: kwargs})
+ resp, body = self.post('servers/%s/action' % str(server_id),
+ post_body, self.headers)
+ if response_key is not None:
+ body = json.loads(body)[response_key]
+ return resp, body
+
+ def change_password(self, server_id, admin_password):
+ """Changes the root password for the server."""
+ return self.action(server_id, 'change_password', None,
+ admin_password=admin_password)
+
+ def reboot(self, server_id, reboot_type):
+ """Reboots a server."""
+ return self.action(server_id, 'reboot', None, type=reboot_type)
+
+ def rebuild(self, server_id, image_ref, **kwargs):
+ """Rebuilds a server with a new image."""
+ kwargs['image_ref'] = image_ref
+ if 'disk_config' in kwargs:
+ kwargs['os-disk-config:disk_config'] = kwargs['disk_config']
+ del kwargs['disk_config']
+ return self.action(server_id, 'rebuild', 'server', **kwargs)
+
+ def resize(self, server_id, flavor_ref, **kwargs):
+ """Changes the flavor of a server."""
+ kwargs['flavor_ref'] = flavor_ref
+ if 'disk_config' in kwargs:
+ kwargs['os-disk-config:disk_config'] = kwargs['disk_config']
+ del kwargs['disk_config']
+ return self.action(server_id, 'resize', None, **kwargs)
+
+ def confirm_resize(self, server_id, **kwargs):
+ """Confirms the flavor change for a server."""
+ return self.action(server_id, 'confirm_resize', None, **kwargs)
+
+ def revert_resize(self, server_id, **kwargs):
+ """Reverts a server back to its original flavor."""
+ return self.action(server_id, 'revert_resize', None, **kwargs)
+
+ def create_image(self, server_id, name, meta=None):
+ """Creates an image of the original server."""
+
+ post_body = {
+ 'create_image': {
+ 'name': name,
+ }
+ }
+
+ if meta is not None:
+ post_body['create_image']['metadata'] = meta
+
+ post_body = json.dumps(post_body)
+ resp, body = self.post('servers/%s/action' % str(server_id),
+ post_body, self.headers)
+ return resp, body
+
+ def list_server_metadata(self, server_id):
+ resp, body = self.get("servers/%s/metadata" % str(server_id))
+ body = json.loads(body)
+ return resp, body['metadata']
+
+ def set_server_metadata(self, server_id, meta, no_metadata_field=False):
+ if no_metadata_field:
+ post_body = ""
+ else:
+ post_body = json.dumps({'metadata': meta})
+ resp, body = self.put('servers/%s/metadata' % str(server_id),
+ post_body, self.headers)
+ body = json.loads(body)
+ return resp, body['metadata']
+
+ def update_server_metadata(self, server_id, meta):
+ post_body = json.dumps({'metadata': meta})
+ resp, body = self.post('servers/%s/metadata' % str(server_id),
+ post_body, self.headers)
+ body = json.loads(body)
+ return resp, body['metadata']
+
+ def get_server_metadata_item(self, server_id, key):
+ resp, body = self.get("servers/%s/metadata/%s" % (str(server_id), key))
+ body = json.loads(body)
+ return resp, body['metadata']
+
+ def set_server_metadata_item(self, server_id, key, meta):
+ post_body = json.dumps({'metadata': meta})
+ resp, body = self.put('servers/%s/metadata/%s' % (str(server_id), key),
+ post_body, self.headers)
+ body = json.loads(body)
+ return resp, body['metadata']
+
+ def delete_server_metadata_item(self, server_id, key):
+ resp, body = self.delete("servers/%s/metadata/%s" %
+ (str(server_id), key))
+ return resp, body
+
+ def stop(self, server_id, **kwargs):
+ return self.action(server_id, 'stop', None, **kwargs)
+
+ def start(self, server_id, **kwargs):
+ return self.action(server_id, 'start', None, **kwargs)
+
+ def attach_volume(self, server_id, volume_id, device='/dev/vdz'):
+ """Attaches a volume to a server instance."""
+ return self.action(server_id, 'attach', None, volume_id=volume_id,
+ device=device)
+
+ def detach_volume(self, server_id, volume_id):
+ """Detaches a volume from a server instance."""
+ return self.action(server_id, 'detach', None, volume_id=volume_id)
+
+ def add_security_group(self, server_id, name):
+ """Adds a security group to the server."""
+ return self.action(server_id, 'add_security_group', None, name=name)
+
+ def remove_security_group(self, server_id, name):
+ """Removes a security group from the server."""
+ return self.action(server_id, 'remove_security_group', None, name=name)
+
+ def live_migrate_server(self, server_id, dest_host, use_block_migration):
+ """This should be called with administrator privileges ."""
+
+ migrate_params = {
+ "disk_over_commit": False,
+ "block_migration": use_block_migration,
+ "host": dest_host
+ }
+
+ req_body = json.dumps({'migrate_live': migrate_params})
+
+ resp, body = self.post("servers/%s/action" % str(server_id),
+ req_body, self.headers)
+ return resp, body
+
+ def migrate_server(self, server_id, **kwargs):
+ """Migrates a server to a new host."""
+ return self.action(server_id, 'migrate', None, **kwargs)
+
+ def lock_server(self, server_id, **kwargs):
+ """Locks the given server."""
+ return self.action(server_id, 'lock', None, **kwargs)
+
+ def unlock_server(self, server_id, **kwargs):
+ """UNlocks the given server."""
+ return self.action(server_id, 'unlock', None, **kwargs)
+
+ def suspend_server(self, server_id, **kwargs):
+ """Suspends the provded server."""
+ return self.action(server_id, 'suspend', None, **kwargs)
+
+ def resume_server(self, server_id, **kwargs):
+ """Un-suspends the provded server."""
+ return self.action(server_id, 'resume', None, **kwargs)
+
+ def pause_server(self, server_id, **kwargs):
+ """Pauses the provded server."""
+ return self.action(server_id, 'pause', None, **kwargs)
+
+ def unpause_server(self, server_id, **kwargs):
+ """Un-pauses the provded server."""
+ return self.action(server_id, 'unpause', None, **kwargs)
+
+ def reset_state(self, server_id, state='error'):
+ """Resets the state of a server to active/error."""
+ return self.action(server_id, 'reset_state', None, state=state)
+
+ def get_console_output(self, server_id, length):
+ return self.action(server_id, 'get_console_output', 'output',
+ length=length)
+
+ def rescue_server(self, server_id, admin_password=None):
+ """Rescue the provided server."""
+ return self.action(server_id, 'rescue', None,
+ admin_password=admin_password)
+
+ def unrescue_server(self, server_id):
+ """Unrescue the provided server."""
+ return self.action(server_id, 'unrescue', None)
+
+ def get_server_diagnostics(self, server_id):
+ """Get the usage data for a server."""
+ resp, body = self.get("servers/%s/os-server-diagnostics" %
+ str(server_id))
+ return resp, json.loads(body)
+
+ def list_instance_actions(self, server_id):
+ """List the provided server action."""
+ resp, body = self.get("servers/%s/os-instance-actions" %
+ str(server_id))
+ body = json.loads(body)
+ return resp, body['instance_actions']
+
+ def get_instance_action(self, server_id, request_id):
+ """Returns the action details of the provided server."""
+ resp, body = self.get("servers/%s/os-instance-actions/%s" %
+ (str(server_id), str(request_id)))
+ body = json.loads(body)
+ return resp, body['instance_action']
diff --git a/tempest/services/compute/v3/json/services_client.py b/tempest/services/compute/v3/json/services_client.py
new file mode 100644
index 0000000..41564e5
--- /dev/null
+++ b/tempest/services/compute/v3/json/services_client.py
@@ -0,0 +1,71 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 NEC Corporation
+# Copyright 2013 IBM Corp.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import json
+import urllib
+
+from tempest.common.rest_client import RestClient
+
+
+class ServicesV3ClientJSON(RestClient):
+
+ def __init__(self, config, username, password, auth_url, tenant_name=None):
+ super(ServicesV3ClientJSON, self).__init__(config, username, password,
+ auth_url, tenant_name)
+ self.service = self.config.compute.catalog_v3_type
+
+ def list_services(self, params=None):
+ url = 'os-services'
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+
+ resp, body = self.get(url)
+ body = json.loads(body)
+ return resp, body['services']
+
+ def enable_service(self, host_name, binary):
+ """
+ Enable service on a host
+ host_name: Name of host
+ binary: Service binary
+ """
+ post_body = json.dumps({
+ 'service': {
+ 'binary': binary,
+ 'host': host_name
+ }
+ })
+ resp, body = self.put('os-services/enable', post_body, self.headers)
+ body = json.loads(body)
+ return resp, body['service']
+
+ def disable_service(self, host_name, binary):
+ """
+ Disable service on a host
+ host_name: Name of host
+ binary: Service binary
+ """
+ post_body = json.dumps({
+ 'service': {
+ 'binary': binary,
+ 'host': host_name
+ }
+ })
+ resp, body = self.put('os-services/disable', post_body, self.headers)
+ body = json.loads(body)
+ return resp, body['service']
diff --git a/tempest/services/compute/v3/json/tenant_usages_client.py b/tempest/services/compute/v3/json/tenant_usages_client.py
new file mode 100644
index 0000000..4dd6964
--- /dev/null
+++ b/tempest/services/compute/v3/json/tenant_usages_client.py
@@ -0,0 +1,47 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 NEC Corporation
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import json
+import urllib
+
+from tempest.common.rest_client import RestClient
+
+
+class TenantUsagesClientJSON(RestClient):
+
+ def __init__(self, config, username, password, auth_url, tenant_name=None):
+ super(TenantUsagesClientJSON, self).__init__(
+ config, username, password, auth_url, tenant_name)
+ self.service = self.config.compute.catalog_type
+
+ def list_tenant_usages(self, params=None):
+ url = 'os-simple-tenant-usage'
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+
+ resp, body = self.get(url)
+ body = json.loads(body)
+ return resp, body['tenant_usages'][0]
+
+ def get_tenant_usage(self, tenant_id, params=None):
+ url = 'os-simple-tenant-usage/%s' % tenant_id
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+
+ resp, body = self.get(url)
+ body = json.loads(body)
+ return resp, body['tenant_usage']
diff --git a/tempest/services/compute/v3/xml/__init__.py b/tempest/services/compute/v3/xml/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/services/compute/v3/xml/__init__.py
diff --git a/tempest/services/compute/v3/xml/availability_zone_client.py b/tempest/services/compute/v3/xml/availability_zone_client.py
new file mode 100644
index 0000000..35fb2b1
--- /dev/null
+++ b/tempest/services/compute/v3/xml/availability_zone_client.py
@@ -0,0 +1,43 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 NEC Corporation
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from lxml import etree
+
+from tempest.common.rest_client import RestClientXML
+from tempest.services.compute.xml.common import xml_to_json
+
+
+class AvailabilityZoneV3ClientXML(RestClientXML):
+
+ def __init__(self, config, username, password, auth_url, tenant_name=None):
+ super(AvailabilityZoneV3ClientXML, self).__init__(config, username,
+ password, auth_url,
+ tenant_name)
+ self.service = self.config.compute.catalog_v3_type
+
+ def _parse_array(self, node):
+ return [xml_to_json(x) for x in node]
+
+ def get_availability_zone_list(self):
+ resp, body = self.get('os-availability-zone', self.headers)
+ availability_zone = self._parse_array(etree.fromstring(body))
+ return resp, availability_zone
+
+ def get_availability_zone_list_detail(self):
+ resp, body = self.get('os-availability-zone/detail', self.headers)
+ availability_zone = self._parse_array(etree.fromstring(body))
+ return resp, availability_zone
diff --git a/tempest/services/compute/v3/xml/extensions_client.py b/tempest/services/compute/v3/xml/extensions_client.py
new file mode 100644
index 0000000..e03251c
--- /dev/null
+++ b/tempest/services/compute/v3/xml/extensions_client.py
@@ -0,0 +1,45 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 OpenStack Foundation
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from lxml import etree
+from tempest.common.rest_client import RestClientXML
+from tempest.services.compute.xml.common import xml_to_json
+
+
+class ExtensionsV3ClientXML(RestClientXML):
+
+ def __init__(self, config, username, password, auth_url, tenant_name=None):
+ super(ExtensionsV3ClientXML, self).__init__(config, username, password,
+ auth_url, tenant_name)
+ self.service = self.config.compute.catalog_v3_type
+
+ def _parse_array(self, node):
+ array = []
+ for child in node:
+ array.append(xml_to_json(child))
+ return array
+
+ def list_extensions(self):
+ url = 'extensions'
+ resp, body = self.get(url, self.headers)
+ body = self._parse_array(etree.fromstring(body))
+ return resp, {'extensions': body}
+
+ def is_enabled(self, extension):
+ _, extensions = self.list_extensions()
+ exts = extensions['extensions']
+ return any([e for e in exts if e['name'] == extension])
diff --git a/tempest/services/compute/v3/xml/interfaces_client.py b/tempest/services/compute/v3/xml/interfaces_client.py
new file mode 100644
index 0000000..870c130
--- /dev/null
+++ b/tempest/services/compute/v3/xml/interfaces_client.py
@@ -0,0 +1,108 @@
+# Copyright 2013 IBM Corp.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import time
+
+from lxml import etree
+
+from tempest.common.rest_client import RestClientXML
+from tempest import exceptions
+from tempest.services.compute.xml.common import Document
+from tempest.services.compute.xml.common import Element
+from tempest.services.compute.xml.common import Text
+from tempest.services.compute.xml.common import xml_to_json
+
+
+class InterfacesV3ClientXML(RestClientXML):
+
+ def __init__(self, config, username, password, auth_url, tenant_name=None):
+ super(InterfacesV3ClientXML, self).__init__(config, username, password,
+ auth_url, tenant_name)
+ self.service = self.config.compute.catalog_v3_type
+
+ def _process_xml_interface(self, node):
+ iface = xml_to_json(node)
+ # NOTE(danms): if multiple addresses per interface is ever required,
+ # xml_to_json will need to be fixed or replaced in this case
+ iface['fixed_ips'] = [dict(iface['fixed_ips']['fixed_ip'].items())]
+ return iface
+
+ def list_interfaces(self, server):
+ resp, body = self.get('servers/%s/os-attach-interfaces' % server,
+ self.headers)
+ node = etree.fromstring(body)
+ interfaces = [self._process_xml_interface(x)
+ for x in node.getchildren()]
+ return resp, interfaces
+
+ def create_interface(self, server, port_id=None, network_id=None,
+ fixed_ip=None):
+ doc = Document()
+ iface = Element('interface_attachment')
+ if port_id:
+ _port_id = Element('port_id')
+ _port_id.append(Text(port_id))
+ iface.append(_port_id)
+ if network_id:
+ _network_id = Element('net_id')
+ _network_id.append(Text(network_id))
+ iface.append(_network_id)
+ if fixed_ip:
+ _fixed_ips = Element('fixed_ips')
+ _fixed_ip = Element('fixed_ip')
+ _ip_address = Element('ip_address')
+ _ip_address.append(Text(fixed_ip))
+ _fixed_ip.append(_ip_address)
+ _fixed_ips.append(_fixed_ip)
+ iface.append(_fixed_ips)
+ doc.append(iface)
+ resp, body = self.post('servers/%s/os-attach-interfaces' % server,
+ headers=self.headers,
+ body=str(doc))
+ body = self._process_xml_interface(etree.fromstring(body))
+ return resp, body
+
+ def show_interface(self, server, port_id):
+ resp, body =\
+ self.get('servers/%s/os-attach-interfaces/%s' % (server, port_id),
+ self.headers)
+ body = self._process_xml_interface(etree.fromstring(body))
+ return resp, body
+
+ def delete_interface(self, server, port_id):
+ resp, body =\
+ self.delete('servers/%s/os-attach-interfaces/%s' % (server,
+ port_id))
+ return resp, body
+
+ def wait_for_interface_status(self, server, port_id, status):
+ """Waits for a interface to reach a given status."""
+ resp, body = self.show_interface(server, port_id)
+ interface_status = body['port_state']
+ start = int(time.time())
+
+ while(interface_status != status):
+ time.sleep(self.build_interval)
+ resp, body = self.show_interface(server, port_id)
+ interface_status = body['port_state']
+
+ timed_out = int(time.time()) - start >= self.build_timeout
+
+ if interface_status != status and timed_out:
+ message = ('Interface %s failed to reach %s status within '
+ 'the required time (%s s).' %
+ (port_id, status, self.build_timeout))
+ raise exceptions.TimeoutException(message)
+ return resp, body
diff --git a/tempest/services/compute/v3/xml/servers_client.py b/tempest/services/compute/v3/xml/servers_client.py
new file mode 100644
index 0000000..2ad5849
--- /dev/null
+++ b/tempest/services/compute/v3/xml/servers_client.py
@@ -0,0 +1,625 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright 2012 IBM Corp.
+# Copyright 2013 Hewlett-Packard Development Company, L.P.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import time
+import urllib
+
+from lxml import etree
+
+from tempest.common.rest_client import RestClientXML
+from tempest.common import waiters
+from tempest import exceptions
+from tempest.openstack.common import log as logging
+from tempest.services.compute.xml.common import Document
+from tempest.services.compute.xml.common import Element
+from tempest.services.compute.xml.common import Text
+from tempest.services.compute.xml.common import xml_to_json
+from tempest.services.compute.xml.common import XMLNS_V3
+
+
+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)
+ if ip.get('type'):
+ ip['type'] = ip.get('type')
+ if ip.get('mac_addr'):
+ ip['mac_addr'] = ip.get('mac_addr')
+ return ip
+
+
+def _translate_network_xml_to_json(network):
+ return [_translate_ip_xml_json(ip.attrib)
+ for ip in network.findall('{%s}ip' % XMLNS_V3)]
+
+
+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_V3))
+
+
+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/v3">
+ <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_V3}
+ 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)
+ disk_config = ('{http://docs.openstack.org'
+ '/compute/ext/disk_config/api/v3}disk_config')
+ terminated_at = ('{http://docs.openstack.org/'
+ 'compute/ext/os-server-usage/api/v3}terminated_at')
+ launched_at = ('{http://docs.openstack.org'
+ '/compute/ext/os-server-usage/api/v3}launched_at')
+ power_state = ('{http://docs.openstack.org'
+ '/compute/ext/extended_status/api/v3}power_state')
+ availability_zone = ('{http://docs.openstack.org'
+ '/compute/ext/extended_availability_zone/api/v3}'
+ 'availability_zone')
+ vm_state = ('{http://docs.openstack.org'
+ '/compute/ext/extended_status/api/v3}vm_state')
+ task_state = ('{http://docs.openstack.org'
+ '/compute/ext/extended_status/api/v3}task_state')
+ if disk_config in json:
+ json['os-disk-config:disk_config'] = json.pop(disk_config)
+ if terminated_at in json:
+ json['os-server-usage:terminated_at'] = json.pop(terminated_at)
+ if launched_at in json:
+ json['os-server-usage:launched_at'] = json.pop(launched_at)
+ if power_state in json:
+ json['os-extended-status:power_state'] = json.pop(power_state)
+ if availability_zone in json:
+ json['os-extended-availability-zone:availability_zone'] = json.pop(
+ availability_zone)
+ if vm_state in json:
+ json['os-extended-status:vm_state'] = json.pop(vm_state)
+ if task_state in json:
+ json['os-extended-status:task_state'] = json.pop(task_state)
+ return json
+
+
+class ServersV3ClientXML(RestClientXML):
+
+ def __init__(self, config, username, password, auth_url,
+ tenant_name=None, auth_version='v2'):
+ super(ServersV3ClientXML, self).__init__(config, username, password,
+ auth_url, tenant_name,
+ auth_version=auth_version)
+ self.service = self.config.compute.catalog_v3_type
+
+ def _parse_key_value(self, node):
+ """Parse <foo key='key'>value</foo> data into {'key': 'value'}."""
+ data = {}
+ for node in node.getchildren():
+ data[node.get('key')] = node.text
+ return data
+
+ def _parse_links(self, node, json):
+ del json['link']
+ json['links'] = []
+ for linknode in node.findall('{http://www.w3.org/2005/Atom}link'):
+ json['links'].append(xml_to_json(linknode))
+
+ def _parse_server(self, 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
+ metadata_tag = body.find('{%s}metadata' % XMLNS_V3)
+ json["metadata"] = self._parse_key_value(metadata_tag)
+ if 'link' in json:
+ self._parse_links(body, json)
+ for sub in ['image', 'flavor']:
+ if sub in json and 'link' in json[sub]:
+ self._parse_links(body, json[sub])
+ return json
+
+ def _parse_xml_virtual_interfaces(self, xml_dom):
+ """
+ Return server's virtual interfaces XML as JSON.
+ """
+ data = {"virtual_interfaces": []}
+ for iface in xml_dom.getchildren():
+ data["virtual_interfaces"].append(
+ {"id": iface.get("id"),
+ "mac_address": iface.get("mac_address")})
+ return data
+
+ def get_server(self, server_id):
+ """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 lock_server(self, server_id, **kwargs):
+ """Locks the given server."""
+ return self.action(server_id, 'lock', None, **kwargs)
+
+ def unlock_server(self, server_id, **kwargs):
+ """Unlocks the given server."""
+ return self.action(server_id, 'unlock', None, **kwargs)
+
+ def suspend_server(self, server_id, **kwargs):
+ """Suspends the provided server."""
+ return self.action(server_id, 'suspend', None, **kwargs)
+
+ def resume_server(self, server_id, **kwargs):
+ """Un-suspends the provided server."""
+ return self.action(server_id, 'resume', None, **kwargs)
+
+ def pause_server(self, server_id, **kwargs):
+ """Pauses the provided server."""
+ return self.action(server_id, 'pause', None, **kwargs)
+
+ def unpause_server(self, server_id, **kwargs):
+ """Un-pauses the provided server."""
+ return self.action(server_id, 'unpause', None, **kwargs)
+
+ def reset_state(self, server_id, state='error'):
+ """Resets the state of a server to active/error."""
+ return self.action(server_id, 'reset_state', None, state=state)
+
+ def delete_server(self, server_id):
+ """Deletes the given server."""
+ return self.delete("servers/%s" % str(server_id))
+
+ def _parse_array(self, node):
+ array = []
+ for child in node.getchildren():
+ array.append(xml_to_json(child))
+ return array
+
+ def list_servers(self, params=None):
+ url = 'servers'
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+
+ resp, body = self.get(url, self.headers)
+ servers = self._parse_array(etree.fromstring(body))
+ return resp, {"servers": servers}
+
+ def list_servers_with_detail(self, params=None):
+ url = 'servers/detail'
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+
+ resp, body = self.get(url, self.headers)
+ servers = self._parse_array(etree.fromstring(body))
+ return resp, {"servers": servers}
+
+ def update_server(self, server_id, name=None, meta=None, access_ip_v4=None,
+ access_ip_v6=None, disk_config=None):
+ doc = Document()
+ server = Element("server")
+ doc.append(server)
+
+ if name is not None:
+ server.add_attr("name", name)
+ if access_ip_v4 is not None:
+ server.add_attr("access_ip_v4", access_ip_v4)
+ if access_ip_v6 is not None:
+ server.add_attr("access_ip_v6", access_ip_v6)
+ if meta is not None:
+ metadata = Element("metadata")
+ server.append(metadata)
+ for k, v in meta:
+ meta = Element("meta", key=k)
+ meta.append(Text(v))
+ metadata.append(meta)
+
+ resp, body = self.put('servers/%s' % str(server_id),
+ str(doc), self.headers)
+ return resp, xml_to_json(etree.fromstring(body))
+
+ def create_server(self, name, image_ref, flavor_ref, **kwargs):
+ """
+ Creates an instance of a server.
+ name (Required): The name of the server.
+ image_ref (Required): Reference to the image used to build the server.
+ flavor_ref (Required): The flavor used to build the server.
+ Following optional keyword arguments are accepted:
+ admin_password: Sets the initial root password.
+ key_name: Key name of keypair that was created earlier.
+ meta: A dictionary of values to be used as metadata.
+ personality: A list of dictionaries for files to be injected into
+ the server.
+ security_groups: A list of security group dicts.
+ networks: A list of network dicts with UUID and fixed_ip.
+ user_data: User data for instance.
+ availability_zone: Availability zone in which to launch instance.
+ access_ip_v4: The IPv4 access address for the server.
+ access_ip_v6: The IPv6 access address for the server.
+ min_count: Count of minimum number of instances to launch.
+ max_count: Count of maximum number of instances to launch.
+ disk_config: Determines if user or admin controls disk configuration.
+ return_reservation_id: Enable/Disable the return of reservation id.
+ """
+ server = Element("server",
+ imageRef=image_ref,
+ xmlns=XMLNS_V3,
+ flavor_ref=flavor_ref,
+ image_ref=image_ref,
+ name=name)
+ attrs = ["admin_password", "access_ip_v4", "access_ip_v6", "key_name",
+ ("os-user-data:user_data",
+ 'user_data',
+ 'xmlns:os-user-data',
+ "http://docs.openstack.org/compute/ext/userdata/api/v3"),
+ ("os-availability-zone:availability_zone",
+ 'availability_zone',
+ 'xmlns:os-availability-zone',
+ "http://docs.openstack.org/compute/ext/"
+ "availabilityzone/api/v3"),
+ ("os-multiple-create:min_count",
+ 'min_count',
+ 'xmlns:os-multiple-create',
+ "http://docs.openstack.org/compute/ext/"
+ "multiplecreate/api/v3"),
+ ("os-multiple-create:max_count",
+ 'max_count',
+ 'xmlns:os-multiple-create',
+ "http://docs.openstack.org/compute/ext/"
+ "multiplecreate/api/v3"),
+ ("os-multiple-create:return_reservation_id",
+ "return_reservation_id",
+ 'xmlns:os-multiple-create',
+ "http://docs.openstack.org/compute/ext/"
+ "multiplecreate/api/v3"),
+ ("os-disk-config:disk_config",
+ "disk_config",
+ "xmlns:os-disk-config",
+ "http://docs.openstack.org/"
+ "compute/ext/disk_config/api/v3")]
+
+ for attr in attrs:
+ if isinstance(attr, tuple):
+ post_param = attr[0]
+ key = attr[1]
+ value = kwargs.get(key)
+ if value is not None:
+ server.add_attr(attr[2], attr[3])
+ server.add_attr(post_param, value)
+ else:
+ post_param = attr
+ key = attr
+ value = kwargs.get(key)
+ if value is not None:
+ server.add_attr(post_param, value)
+
+ if 'security_groups' in kwargs:
+ secgroups = Element("security_groups")
+ server.append(secgroups)
+ for secgroup in kwargs['security_groups']:
+ s = Element("security_group", name=secgroup['name'])
+ secgroups.append(s)
+
+ if 'networks' in kwargs:
+ networks = Element("networks")
+ server.append(networks)
+ for network in kwargs['networks']:
+ s = Element("network", uuid=network['uuid'],
+ fixed_ip=network['fixed_ip'])
+ networks.append(s)
+
+ if 'meta' in kwargs:
+ metadata = Element("metadata")
+ server.append(metadata)
+ for k, v in kwargs['meta'].items():
+ meta = Element("meta", key=k)
+ meta.append(Text(v))
+ metadata.append(meta)
+
+ if 'personality' in kwargs:
+ personality = Element('personality')
+ server.append(personality)
+ for k in kwargs['personality']:
+ temp = Element('file', path=k['path'])
+ temp.append(Text(k['contents']))
+ personality.append(temp)
+
+ resp, body = self.post('servers', str(Document(server)), self.headers)
+ server = self._parse_server(etree.fromstring(body))
+ return resp, server
+
+ def wait_for_server_status(self, server_id, status, extra_timeout=0):
+ """Waits for a server to reach a given status."""
+ return waiters.wait_for_server_status(self, server_id, status,
+ extra_timeout=extra_timeout)
+
+ def wait_for_server_termination(self, server_id, ignore_error=False):
+ """Waits for server to reach termination."""
+ start_time = int(time.time())
+ while True:
+ try:
+ resp, body = self.get_server(server_id)
+ except exceptions.NotFound:
+ return
+
+ server_status = body['status']
+ if server_status == 'ERROR' and not ignore_error:
+ raise exceptions.BuildErrorException
+
+ if int(time.time()) - start_time >= self.build_timeout:
+ raise exceptions.TimeoutException
+
+ time.sleep(self.build_interval)
+
+ def _parse_network(self, node):
+ addrs = []
+ for child in node.getchildren():
+ addrs.append({'version': int(child.get('version')),
+ 'addr': child.get('addr')})
+ return {node.get('id'): addrs}
+
+ def list_addresses(self, server_id):
+ """Lists all addresses for a server."""
+ resp, body = self.get("servers/%s/ips" % str(server_id), self.headers)
+
+ networks = {}
+ xml_list = etree.fromstring(body)
+ for child in xml_list.getchildren():
+ network = self._parse_network(child)
+ networks.update(**network)
+
+ return resp, networks
+
+ def list_addresses_by_network(self, server_id, network_id):
+ """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)
+ network = self._parse_network(etree.fromstring(body))
+
+ return resp, network
+
+ def action(self, server_id, action_name, response_key, **kwargs):
+ if 'xmlns' not in kwargs:
+ kwargs['xmlns'] = XMLNS_V3
+ doc = Document((Element(action_name, **kwargs)))
+ resp, body = self.post("servers/%s/action" % server_id,
+ str(doc), self.headers)
+ if response_key is not None:
+ body = xml_to_json(etree.fromstring(body))
+ return resp, body
+
+ def change_password(self, server_id, password):
+ return self.action(server_id, "change_password", None,
+ admin_password=password)
+
+ def reboot(self, server_id, reboot_type):
+ return self.action(server_id, "reboot", None, type=reboot_type)
+
+ def rebuild(self, server_id, image_ref, **kwargs):
+ kwargs['image_ref'] = image_ref
+ if 'disk_config' in kwargs:
+ kwargs['os-disk-config:disk_config'] = kwargs['disk_config']
+ del kwargs['disk_config']
+ kwargs['xmlns:os-disk-config'] = "http://docs.openstack.org/"\
+ "compute/ext/disk_config/api/v3"
+ kwargs['xmlns:atom'] = "http://www.w3.org/2005/Atom"
+ if 'xmlns' not in kwargs:
+ kwargs['xmlns'] = XMLNS_V3
+
+ attrs = kwargs.copy()
+ if 'metadata' in attrs:
+ del attrs['metadata']
+ rebuild = Element("rebuild",
+ **attrs)
+
+ if 'metadata' in kwargs:
+ metadata = Element("metadata")
+ rebuild.append(metadata)
+ for k, v in kwargs['metadata'].items():
+ meta = Element("meta", key=k)
+ meta.append(Text(v))
+ metadata.append(meta)
+
+ resp, body = self.post('servers/%s/action' % server_id,
+ str(Document(rebuild)), self.headers)
+ server = self._parse_server(etree.fromstring(body))
+ return resp, server
+
+ def resize(self, server_id, flavor_ref, **kwargs):
+ if 'disk_config' in kwargs:
+ kwargs['os-disk-config:disk_config'] = kwargs['disk_config']
+ del kwargs['disk_config']
+ kwargs['xmlns:os-disk-config'] = "http://docs.openstack.org/"\
+ "compute/ext/disk_config/api/v3"
+ kwargs['xmlns:atom'] = "http://www.w3.org/2005/Atom"
+ kwargs['flavor_ref'] = flavor_ref
+ return self.action(server_id, 'resize', None, **kwargs)
+
+ def confirm_resize(self, server_id, **kwargs):
+ return self.action(server_id, 'confirm_resize', None, **kwargs)
+
+ def revert_resize(self, server_id, **kwargs):
+ return self.action(server_id, 'revert_resize', None, **kwargs)
+
+ def stop(self, server_id, **kwargs):
+ return self.action(server_id, 'stop', None, **kwargs)
+
+ def start(self, server_id, **kwargs):
+ return self.action(server_id, 'start', None, **kwargs)
+
+ def create_image(self, server_id, name, meta=None):
+ """Creates an image of the original server."""
+ post_body = Element('create_image', name=name)
+
+ if meta:
+ metadata = Element('metadata')
+ post_body.append(metadata)
+ for k, v in meta.items():
+ data = Element('meta', key=k)
+ data.append(Text(v))
+ metadata.append(data)
+ resp, body = self.post('servers/%s/action' % str(server_id),
+ str(Document(post_body)), self.headers)
+ return resp, body
+
+ def add_security_group(self, server_id, name):
+ return self.action(server_id, 'add_security_group', None, name=name)
+
+ def remove_security_group(self, server_id, name):
+ return self.action(server_id, 'remove_security_group', None, name=name)
+
+ def live_migrate_server(self, server_id, dest_host, use_block_migration):
+ """This should be called with administrator privileges ."""
+
+ req_body = Element("migrate_live",
+ xmlns=XMLNS_V3,
+ disk_over_commit=False,
+ block_migration=use_block_migration,
+ host=dest_host)
+
+ resp, body = self.post("servers/%s/action" % str(server_id),
+ str(Document(req_body)), self.headers)
+ return resp, body
+
+ def list_server_metadata(self, server_id):
+ resp, body = self.get("servers/%s/metadata" % str(server_id),
+ self.headers)
+ body = self._parse_key_value(etree.fromstring(body))
+ return resp, body
+
+ def set_server_metadata(self, server_id, meta, no_metadata_field=False):
+ doc = Document()
+ if not no_metadata_field:
+ metadata = Element("metadata")
+ doc.append(metadata)
+ for k, v in meta.items():
+ meta_element = Element("meta", key=k)
+ meta_element.append(Text(v))
+ metadata.append(meta_element)
+ resp, body = self.put('servers/%s/metadata' % str(server_id),
+ str(doc), self.headers)
+ return resp, xml_to_json(etree.fromstring(body))
+
+ def update_server_metadata(self, server_id, meta):
+ doc = Document()
+ metadata = Element("metadata")
+ doc.append(metadata)
+ for k, v in meta.items():
+ meta_element = Element("meta", key=k)
+ meta_element.append(Text(v))
+ metadata.append(meta_element)
+ resp, body = self.post("/servers/%s/metadata" % str(server_id),
+ str(doc), headers=self.headers)
+ body = xml_to_json(etree.fromstring(body))
+ return resp, body
+
+ def get_server_metadata_item(self, server_id, key):
+ resp, body = self.get("servers/%s/metadata/%s" % (str(server_id), key),
+ headers=self.headers)
+ return resp, dict([(etree.fromstring(body).attrib['key'],
+ xml_to_json(etree.fromstring(body)))])
+
+ def set_server_metadata_item(self, server_id, key, meta):
+ doc = Document()
+ for k, v in meta.items():
+ meta_element = Element("meta", key=k)
+ meta_element.append(Text(v))
+ doc.append(meta_element)
+ resp, body = self.put('servers/%s/metadata/%s' % (str(server_id), key),
+ str(doc), self.headers)
+ return resp, xml_to_json(etree.fromstring(body))
+
+ def delete_server_metadata_item(self, server_id, key):
+ resp, body = self.delete("servers/%s/metadata/%s" %
+ (str(server_id), key))
+ return resp, body
+
+ def get_console_output(self, server_id, length):
+ return self.action(server_id, 'get_console_output', 'output',
+ length=length)
+
+ def rescue_server(self, server_id, admin_password=None):
+ """Rescue the provided server."""
+ return self.action(server_id, 'rescue', None,
+ admin_password=admin_password)
+
+ def unrescue_server(self, server_id):
+ """Unrescue the provided server."""
+ return self.action(server_id, 'unrescue', None)
+
+ def attach_volume(self, server_id, volume_id, device='/dev/vdz'):
+ return self.action(server_id, "attach", None, volume_id=volume_id,
+ device=device)
+
+ def detach_volume(self, server_id, volume_id):
+ return self.action(server_id, "detach", None, volume_id=volume_id)
+
+ def get_server_diagnostics(self, server_id):
+ """Get the usage data for a server."""
+ resp, body = self.get("servers/%s/os-server-diagnostics" % server_id,
+ self.headers)
+ body = xml_to_json(etree.fromstring(body))
+ return resp, body
+
+ def list_instance_actions(self, server_id):
+ """List the provided server action."""
+ resp, body = self.get("servers/%s/os-instance-actions" % server_id,
+ self.headers)
+ body = self._parse_array(etree.fromstring(body))
+ return resp, body
+
+ def get_instance_action(self, server_id, request_id):
+ """Returns the action details of the provided server."""
+ resp, body = self.get("servers/%s/os-instance-actions/%s" %
+ (server_id, request_id), self.headers)
+ body = xml_to_json(etree.fromstring(body))
+ return resp, body
diff --git a/tempest/services/compute/v3/xml/services_client.py b/tempest/services/compute/v3/xml/services_client.py
new file mode 100644
index 0000000..855641b
--- /dev/null
+++ b/tempest/services/compute/v3/xml/services_client.py
@@ -0,0 +1,73 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 NEC Corporation
+# Copyright 2013 IBM Corp.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import urllib
+
+from lxml import etree
+from tempest.common.rest_client import RestClientXML
+from tempest.services.compute.xml.common import Document
+from tempest.services.compute.xml.common import Element
+from tempest.services.compute.xml.common import xml_to_json
+
+
+class ServicesV3ClientXML(RestClientXML):
+
+ def __init__(self, config, username, password, auth_url, tenant_name=None):
+ super(ServicesV3ClientXML, self).__init__(config, username, password,
+ auth_url, tenant_name)
+ self.service = self.config.compute.catalog_v3_type
+
+ def list_services(self, params=None):
+ url = 'os-services'
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+
+ resp, body = self.get(url, self.headers)
+ node = etree.fromstring(body)
+ body = [xml_to_json(x) for x in node.getchildren()]
+ return resp, body
+
+ def enable_service(self, host_name, binary):
+ """
+ Enable service on a host
+ host_name: Name of host
+ binary: Service binary
+ """
+ post_body = Element("service")
+ post_body.add_attr('binary', binary)
+ post_body.add_attr('host', host_name)
+
+ resp, body = self.put('os-services/enable', str(Document(post_body)),
+ self.headers)
+ body = xml_to_json(etree.fromstring(body))
+ return resp, body
+
+ def disable_service(self, host_name, binary):
+ """
+ Disable service on a host
+ host_name: Name of host
+ binary: Service binary
+ """
+ post_body = Element("service")
+ post_body.add_attr('binary', binary)
+ post_body.add_attr('host', host_name)
+
+ resp, body = self.put('os-services/disable', str(Document(post_body)),
+ self.headers)
+ body = xml_to_json(etree.fromstring(body))
+ return resp, body
diff --git a/tempest/services/compute/v3/xml/tenant_usages_client.py b/tempest/services/compute/v3/xml/tenant_usages_client.py
new file mode 100644
index 0000000..cb92324
--- /dev/null
+++ b/tempest/services/compute/v3/xml/tenant_usages_client.py
@@ -0,0 +1,54 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 NEC Corporation
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import urllib
+
+from lxml import etree
+
+from tempest.common.rest_client import RestClientXML
+from tempest.services.compute.xml.common import xml_to_json
+
+
+class TenantUsagesClientXML(RestClientXML):
+
+ def __init__(self, config, username, password, auth_url, tenant_name=None):
+ super(TenantUsagesClientXML, self).__init__(config, username,
+ password, auth_url,
+ tenant_name)
+ self.service = self.config.compute.catalog_type
+
+ def _parse_array(self, node):
+ json = xml_to_json(node)
+ return json
+
+ def list_tenant_usages(self, params=None):
+ url = 'os-simple-tenant-usage'
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+
+ resp, body = self.get(url, self.headers)
+ tenant_usage = self._parse_array(etree.fromstring(body))
+ return resp, tenant_usage['tenant_usage']
+
+ def get_tenant_usage(self, tenant_id, params=None):
+ url = 'os-simple-tenant-usage/%s' % tenant_id
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+
+ resp, body = self.get(url, self.headers)
+ tenant_usage = self._parse_array(etree.fromstring(body))
+ return resp, tenant_usage
diff --git a/tempest/services/compute/xml/aggregates_client.py b/tempest/services/compute/xml/aggregates_client.py
index 8ef0af6..5faaff5 100644
--- a/tempest/services/compute/xml/aggregates_client.py
+++ b/tempest/services/compute/xml/aggregates_client.py
@@ -21,6 +21,7 @@
from tempest import exceptions
from tempest.services.compute.xml.common import Document
from tempest.services.compute.xml.common import Element
+from tempest.services.compute.xml.common import Text
from tempest.services.compute.xml.common import xml_to_json
@@ -112,3 +113,18 @@
self.headers)
aggregate = self._format_aggregate(etree.fromstring(body))
return resp, aggregate
+
+ def set_metadata(self, aggregate_id, meta):
+ """Replaces the aggregate's existing metadata with new metadata."""
+ post_body = Element("set_metadata")
+ metadata = Element("metadata")
+ post_body.append(metadata)
+ for k, v in meta.items():
+ meta = Element(k)
+ meta.append(Text(v))
+ metadata.append(meta)
+ resp, body = self.post('os-aggregates/%s/action' % aggregate_id,
+ str(Document(post_body)),
+ self.headers)
+ aggregate = self._format_aggregate(etree.fromstring(body))
+ return resp, aggregate
diff --git a/tempest/services/compute/xml/certificates_client.py b/tempest/services/compute/xml/certificates_client.py
new file mode 100644
index 0000000..7523352
--- /dev/null
+++ b/tempest/services/compute/xml/certificates_client.py
@@ -0,0 +1,40 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 IBM Corp
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+
+from tempest.common.rest_client import RestClientXML
+
+
+class CertificatesClientXML(RestClientXML):
+
+ def __init__(self, config, username, password, auth_url, tenant_name=None):
+ super(CertificatesClientXML, self).__init__(config, username, password,
+ auth_url, tenant_name)
+ self.service = self.config.compute.catalog_type
+
+ def get_certificate(self, id):
+ url = "os-certificates/%s" % (id)
+ resp, body = self.get(url, self.headers)
+ body = self._parse_resp(body)
+ return resp, body
+
+ def create_certificate(self):
+ """create certificates."""
+ url = "os-certificates"
+ resp, body = self.post(url, None, self.headers)
+ body = self._parse_resp(body)
+ return resp, body
diff --git a/tempest/services/compute/xml/common.py b/tempest/services/compute/xml/common.py
index 84b56c2..860dd5b 100644
--- a/tempest/services/compute/xml/common.py
+++ b/tempest/services/compute/xml/common.py
@@ -18,6 +18,7 @@
import collections
XMLNS_11 = "http://docs.openstack.org/compute/api/v1.1"
+XMLNS_V3 = "http://docs.openstack.org/compute/api/v1.1"
# NOTE(danms): This is just a silly implementation to help make generating
@@ -36,7 +37,9 @@
self._elements.append(element)
def __str__(self):
- args = " ".join(['%s="%s"' % (k, v) for k, v in self._attrs.items()])
+ args = " ".join(['%s="%s"' %
+ (k, v if v is not None else "")
+ for k, v in self._attrs.items()])
string = '<%s %s' % (self.element_name, args)
if not self._elements:
string += '/>'
@@ -78,7 +81,9 @@
Element.__init__(self, '?xml', *args, **kwargs)
def __str__(self):
- args = " ".join(['%s="%s"' % (k, v) for k, v in self._attrs.items()])
+ args = " ".join(['%s="%s"' %
+ (k, v if v is not None else "")
+ for k, v in self._attrs.items()])
string = '<?xml %s?>\n' % args
for element in self._elements:
string += str(element)
diff --git a/tempest/services/compute/xml/fixed_ips_client.py b/tempest/services/compute/xml/fixed_ips_client.py
index ef023f0..bf2de38 100644
--- a/tempest/services/compute/xml/fixed_ips_client.py
+++ b/tempest/services/compute/xml/fixed_ips_client.py
@@ -15,13 +15,11 @@
# License for the specific language governing permissions and limitations
# under the License.
-from lxml import etree
from tempest.common.rest_client import RestClientXML
from tempest.services.compute.xml.common import Document
from tempest.services.compute.xml.common import Element
from tempest.services.compute.xml.common import Text
-from tempest.services.compute.xml.common import xml_to_json
class FixedIPsClientXML(RestClientXML):
@@ -31,10 +29,6 @@
auth_url, tenant_name)
self.service = self.config.compute.catalog_type
- def _parse_fixed_ip_details(self, body):
- body = xml_to_json(etree.fromstring(body))
- return body
-
def get_fixed_ip_details(self, fixed_ip):
url = "os-fixed-ips/%s" % (fixed_ip)
resp, body = self.get(url, self.headers)
diff --git a/tempest/services/compute/xml/flavors_client.py b/tempest/services/compute/xml/flavors_client.py
index c5886ee..363c1a8 100644
--- a/tempest/services/compute/xml/flavors_client.py
+++ b/tempest/services/compute/xml/flavors_client.py
@@ -22,6 +22,7 @@
from tempest.common.rest_client import RestClientXML
from tempest.services.compute.xml.common import Document
from tempest.services.compute.xml.common import Element
+from tempest.services.compute.xml.common import Text
from tempest.services.compute.xml.common import xml_to_json
from tempest.services.compute.xml.common import XMLNS_11
@@ -29,7 +30,7 @@
XMLNS_OS_FLV_EXT_DATA = \
"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"
+ "http://docs.openstack.org/compute/ext/flavor_access/api/v2"
class FlavorsClientXML(RestClientXML):
@@ -49,6 +50,10 @@
if k == '{%s}ephemeral' % XMLNS_OS_FLV_EXT_DATA:
k = 'OS-FLV-EXT-DATA:ephemeral'
+ if k == '{%s}is_public' % XMLNS_OS_FLV_ACCESS:
+ k = 'os-flavor-access:is_public'
+ v = True if v == 'True' else False
+
if k == 'extra_specs':
k = 'OS-FLV-WITH-EXT-SPECS:extra_specs'
flavor[k] = dict(v)
@@ -148,6 +153,31 @@
body = xml_to_json(etree.fromstring(body))
return resp, body
+ def get_flavor_extra_spec_with_key(self, flavor_id, key):
+ """Gets extra Specs key-value of the mentioned flavor and key."""
+ resp, xml_body = self.get('flavors/%s/os-extra_specs/%s' %
+ (str(flavor_id), key), self.headers)
+ body = {}
+ element = etree.fromstring(xml_body)
+ key = element.get('key')
+ body[key] = xml_to_json(element)
+ return resp, body
+
+ def update_flavor_extra_spec(self, flavor_id, key, **kwargs):
+ """Update extra Specs details of the mentioned flavor and key."""
+ doc = Document()
+ for (k, v) in kwargs.items():
+ element = Element(k)
+ doc.append(element)
+ value = Text(v)
+ element.append(value)
+
+ resp, body = self.put('flavors/%s/os-extra_specs/%s' %
+ (flavor_id, key),
+ str(doc), self.headers)
+ body = xml_to_json(etree.fromstring(body))
+ return resp, {key: body}
+
def unset_flavor_extra_spec(self, flavor_id, key):
"""Unsets an extra spec based on the mentioned flavor and key."""
return self.delete('flavors/%s/os-extra_specs/%s' % (str(flavor_id),
@@ -156,6 +186,13 @@
def _parse_array_access(self, node):
return [xml_to_json(x) for x in node]
+ def list_flavor_access(self, flavor_id):
+ """Gets flavor access information given the flavor id."""
+ resp, body = self.get('flavors/%s/os-flavor-access' % str(flavor_id),
+ self.headers)
+ body = self._parse_array(etree.fromstring(body))
+ return resp, body
+
def add_flavor_access(self, flavor_id, tenant_id):
"""Add flavor access for the specified tenant."""
doc = Document()
diff --git a/tempest/services/compute/xml/hosts_client.py b/tempest/services/compute/xml/hosts_client.py
index 9743143..f7d7b0a 100644
--- a/tempest/services/compute/xml/hosts_client.py
+++ b/tempest/services/compute/xml/hosts_client.py
@@ -18,6 +18,8 @@
from lxml import etree
from tempest.common.rest_client import RestClientXML
+from tempest.services.compute.xml.common import Document
+from tempest.services.compute.xml.common import Element
from tempest.services.compute.xml.common import xml_to_json
@@ -47,3 +49,46 @@
node = etree.fromstring(body)
body = [xml_to_json(x) for x in node.getchildren()]
return resp, body
+
+ def update_host(self, hostname, status=None, maintenance_mode=None,
+ **kwargs):
+ """Update a host."""
+
+ request_body = Element(status=status,
+ maintenance_mode=maintenance_mode)
+ if kwargs:
+ for k, v in kwargs.iteritem():
+ request_body.add_attr(k, v)
+ resp, body = self.put("os-hosts/%s" % str(hostname),
+ str(Document(request_body)),
+ self.headers)
+ node = etree.fromstring(body)
+ body = [xml_to_json(x) for x in node.getchildren()]
+ return resp, body
+
+ def startup_host(self, hostname):
+ """Startup a host."""
+
+ resp, body = self.get("os-hosts/%s/startup" % str(hostname),
+ self.headers)
+ node = etree.fromstring(body)
+ body = [xml_to_json(x) for x in node.getchildren()]
+ return resp, body
+
+ def shutdown_host(self, hostname):
+ """Shutdown a host."""
+
+ resp, body = self.get("os-hosts/%s/shutdown" % str(hostname),
+ self.headers)
+ node = etree.fromstring(body)
+ body = [xml_to_json(x) for x in node.getchildren()]
+ return resp, body
+
+ def reboot_host(self, hostname):
+ """Reboot a host."""
+
+ resp, body = self.get("os-hosts/%s/reboot" % str(hostname),
+ self.headers)
+ node = etree.fromstring(body)
+ body = [xml_to_json(x) for x in node.getchildren()]
+ return resp, body
diff --git a/tempest/services/compute/xml/hypervisor_client.py b/tempest/services/compute/xml/hypervisor_client.py
index 3c4f2b8..c10fed9 100644
--- a/tempest/services/compute/xml/hypervisor_client.py
+++ b/tempest/services/compute/xml/hypervisor_client.py
@@ -70,3 +70,10 @@
self.headers)
uptime = xml_to_json(etree.fromstring(body))
return resp, uptime
+
+ def search_hypervisor(self, hyper_name):
+ """Search specified hypervisor."""
+ resp, body = self.get('os-hypervisors/%s/search' % hyper_name,
+ self.headers)
+ hypervisors = self._parse_array(etree.fromstring(body))
+ return resp, hypervisors
diff --git a/tempest/services/compute/xml/instance_usage_audit_log_client.py b/tempest/services/compute/xml/instance_usage_audit_log_client.py
new file mode 100644
index 0000000..175997b
--- /dev/null
+++ b/tempest/services/compute/xml/instance_usage_audit_log_client.py
@@ -0,0 +1,41 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 IBM Corporation
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from lxml import etree
+
+from tempest.common.rest_client import RestClientXML
+from tempest.services.compute.xml.common import xml_to_json
+
+
+class InstanceUsagesAuditLogClientXML(RestClientXML):
+
+ def __init__(self, config, username, password, auth_url, tenant_name=None):
+ super(InstanceUsagesAuditLogClientXML, self).__init__(
+ config, username, password, auth_url, tenant_name)
+ self.service = self.config.compute.catalog_type
+
+ def list_instance_usage_audit_logs(self):
+ url = 'os-instance_usage_audit_log'
+ resp, body = self.get(url, self.headers)
+ instance_usage_audit_logs = xml_to_json(etree.fromstring(body))
+ return resp, instance_usage_audit_logs
+
+ def get_instance_usage_audit_log(self, time_before):
+ url = 'os-instance_usage_audit_log/%s' % time_before
+ resp, body = self.get(url, self.headers)
+ instance_usage_audit_log = xml_to_json(etree.fromstring(body))
+ return resp, instance_usage_audit_log
diff --git a/tempest/services/compute/xml/servers_client.py b/tempest/services/compute/xml/servers_client.py
index cb21c61..3e5413c 100644
--- a/tempest/services/compute/xml/servers_client.py
+++ b/tempest/services/compute/xml/servers_client.py
@@ -217,6 +217,14 @@
"""Un-pauses the provided server."""
return self.action(server_id, 'unpause', None, **kwargs)
+ def shelve_server(self, server_id, **kwargs):
+ """Shelves the provided server."""
+ return self.action(server_id, 'shelve', None, **kwargs)
+
+ def unshelve_server(self, server_id, **kwargs):
+ """Un-shelves the provided server."""
+ return self.action(server_id, 'unshelve', None, **kwargs)
+
def reset_state(self, server_id, state='error'):
"""Resets the state of a server to active/error."""
return self.action(server_id, 'os-resetState', None, state=state)
@@ -250,7 +258,7 @@
return resp, {"servers": servers}
def update_server(self, server_id, name=None, meta=None, accessIPv4=None,
- accessIPv6=None):
+ accessIPv6=None, disk_config=None):
doc = Document()
server = Element("server")
doc.append(server)
@@ -261,6 +269,10 @@
server.add_attr("accessIPv4", accessIPv4)
if accessIPv6 is not None:
server.add_attr("accessIPv6", accessIPv6)
+ if disk_config is not None:
+ server.add_attr('xmlns:OS-DCF', "http://docs.openstack.org/"
+ "compute/ext/disk_config/api/v1.1")
+ server.add_attr("OS-DCF:diskConfig", disk_config)
if meta is not None:
metadata = Element("metadata")
server.append(metadata)
@@ -347,9 +359,10 @@
server = self._parse_server(etree.fromstring(body))
return resp, server
- def wait_for_server_status(self, server_id, status):
+ def wait_for_server_status(self, server_id, status, extra_timeout=0):
"""Waits for a server to reach a given status."""
- return waiters.wait_for_server_status(self, server_id, status)
+ return waiters.wait_for_server_status(self, server_id, status,
+ extra_timeout=extra_timeout)
def wait_for_server_termination(self, server_id, ignore_error=False):
"""Waits for server to reach termination."""
@@ -407,10 +420,32 @@
body = xml_to_json(etree.fromstring(body))
return resp, body
+ def create_backup(self, server_id, backup_type, rotation, name):
+ """Backup a server instance."""
+ return self.action(server_id, "createBackup", None,
+ backup_type=backup_type,
+ rotation=rotation,
+ name=name)
+
def change_password(self, server_id, password):
return self.action(server_id, "changePassword", None,
adminPass=password)
+ def get_password(self, server_id):
+ resp, body = self.get("servers/%s/os-server-password" %
+ str(server_id), self.headers)
+ body = xml_to_json(etree.fromstring(body))
+ return resp, body
+
+ def delete_password(self, server_id):
+ """
+ Removes the encrypted server password from the metadata server
+ Note that this does not actually change the instance server
+ password.
+ """
+ return self.delete("servers/%s/os-server-password" %
+ str(server_id))
+
def reboot(self, server_id, reboot_type):
return self.action(server_id, "reboot", None, type=reboot_type)
@@ -494,14 +529,15 @@
body = self._parse_key_value(etree.fromstring(body))
return resp, body
- def set_server_metadata(self, server_id, meta):
+ def set_server_metadata(self, server_id, meta, no_metadata_field=False):
doc = Document()
- metadata = Element("metadata")
- doc.append(metadata)
- for k, v in meta.items():
- meta_element = Element("meta", key=k)
- meta_element.append(Text(v))
- metadata.append(meta_element)
+ if not no_metadata_field:
+ metadata = Element("metadata")
+ doc.append(metadata)
+ for k, v in meta.items():
+ meta_element = Element("meta", key=k)
+ meta_element.append(Text(v))
+ metadata.append(meta_element)
resp, body = self.put('servers/%s/metadata' % str(server_id),
str(doc), self.headers)
return resp, xml_to_json(etree.fromstring(body))
@@ -595,3 +631,11 @@
(server_id, request_id), self.headers)
body = xml_to_json(etree.fromstring(body))
return resp, body
+
+ def force_delete_server(self, server_id, **kwargs):
+ """Force delete a server."""
+ return self.action(server_id, 'forceDelete', None, **kwargs)
+
+ def restore_soft_deleted_server(self, server_id, **kwargs):
+ """Restore a soft-deleted server."""
+ return self.action(server_id, 'restore', None, **kwargs)
diff --git a/tempest/services/identity/json/identity_client.py b/tempest/services/identity/json/identity_client.py
index 18132ed..94045b8 100644
--- a/tempest/services/identity/json/identity_client.py
+++ b/tempest/services/identity/json/identity_client.py
@@ -139,7 +139,7 @@
body = json.loads(body)
return resp, body['tenant']
- def create_user(self, name, password, tenant_id, email):
+ def create_user(self, name, password, tenant_id, email, **kwargs):
"""Create a user."""
post_body = {
'name': name,
@@ -147,6 +147,8 @@
'tenantId': tenant_id,
'email': email
}
+ if kwargs.get('enabled') is not None:
+ post_body['enabled'] = kwargs.get('enabled')
post_body = json.dumps({'user': post_body})
resp, body = self.post('users', post_body, self.headers)
body = json.loads(body)
diff --git a/tempest/services/identity/xml/identity_client.py b/tempest/services/identity/xml/identity_client.py
index 9d44826..9c0a72c 100644
--- a/tempest/services/identity/xml/identity_client.py
+++ b/tempest/services/identity/xml/identity_client.py
@@ -159,7 +159,7 @@
body = self._parse_body(etree.fromstring(body))
return resp, body
- def create_user(self, name, password, tenant_id, email):
+ def create_user(self, name, password, tenant_id, email, **kwargs):
"""Create a user."""
create_user = Element("user",
xmlns=XMLNS,
@@ -167,6 +167,9 @@
password=password,
tenantId=tenant_id,
email=email)
+ if 'enabled' in kwargs:
+ create_user.add_attr('enabled', str(kwargs['enabled']).lower())
+
resp, body = self.post('users', str(Document(create_user)),
self.headers)
body = self._parse_body(etree.fromstring(body))
diff --git a/tempest/services/image/v1/json/image_client.py b/tempest/services/image/v1/json/image_client.py
index 1921d78..9f5a405 100644
--- a/tempest/services/image/v1/json/image_client.py
+++ b/tempest/services/image/v1/json/image_client.py
@@ -175,7 +175,7 @@
def delete_image(self, image_id):
url = 'v1/images/%s' % image_id
- self.delete(url)
+ return self.delete(url)
def image_list(self, **kwargs):
url = 'v1/images'
@@ -187,9 +187,15 @@
body = json.loads(body)
return resp, body['images']
- def image_list_detail(self, **kwargs):
+ def image_list_detail(self, properties=dict(), **kwargs):
url = 'v1/images/detail'
+ params = {}
+ for key, value in properties.items():
+ params['property-%s' % key] = value
+
+ kwargs.update(params)
+
if len(kwargs) > 0:
url += '?%s' % urllib.urlencode(kwargs)
@@ -265,9 +271,12 @@
LOG.info('Value transition from "%s" to "%s"'
'in %d second(s).', old_value,
value, dtime)
- if (value == status):
+ if value == status:
return value
+ if value == 'killed':
+ raise exceptions.ImageKilledException(image_id=image_id,
+ status=status)
if dtime > self.build_timeout:
message = ('Time Limit Exceeded! (%ds)'
'while waiting for %s, '
diff --git a/tempest/services/image/v2/json/image_client.py b/tempest/services/image/v2/json/image_client.py
index f0531ec..3d37267 100644
--- a/tempest/services/image/v2/json/image_client.py
+++ b/tempest/services/image/v2/json/image_client.py
@@ -100,7 +100,7 @@
self._validate_schema(body, type='images')
return resp, body['images']
- def get_image_metadata(self, image_id):
+ def get_image(self, image_id):
url = 'v2/images/%s' % image_id
resp, body = self.get(url)
body = json.loads(body)
@@ -108,7 +108,7 @@
def is_resource_deleted(self, id):
try:
- self.get_image_metadata(id)
+ self.get_image(id)
except exceptions.NotFound:
return True
return False
@@ -124,3 +124,37 @@
url = 'v2/images/%s/file' % image_id
resp, body = self.get(url)
return resp, body
+
+ def add_image_tag(self, image_id, tag):
+ url = 'v2/images/%s/tags/%s' % (image_id, tag)
+ resp, body = self.put(url, body=None, headers=self.headers)
+ return resp, body
+
+ def delete_image_tag(self, image_id, tag):
+ url = 'v2/images/%s/tags/%s' % (image_id, tag)
+ resp, _ = self.delete(url)
+ return resp
+
+ def get_image_membership(self, image_id):
+ url = 'v2/images/%s/members' % image_id
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.expected_success(200, resp)
+ return resp, body
+
+ def add_member(self, image_id, member_id):
+ url = 'v2/images/%s/members' % image_id
+ data = json.dumps({'member': member_id})
+ resp, body = self.post(url, data, self.headers)
+ body = json.loads(body)
+ self.expected_success(200, resp)
+ return resp, body
+
+ def update_member_status(self, image_id, member_id, status):
+ """Valid status are: ``pending``, ``accepted``, ``rejected``."""
+ url = 'v2/images/%s/members/%s' % (image_id, member_id)
+ data = json.dumps({'status': status})
+ resp, body = self.put(url, data, self.headers)
+ body = json.loads(body)
+ self.expected_success(200, resp)
+ return resp, body
diff --git a/tempest/services/network/json/network_client.py b/tempest/services/network/json/network_client.py
index 4b87b91..aa7f2f2 100644
--- a/tempest/services/network/json/network_client.py
+++ b/tempest/services/network/json/network_client.py
@@ -123,8 +123,12 @@
resp, body = self.delete(uri, self.headers)
return resp, body
- def list_ports(self):
+ def list_ports(self, **filters):
uri = '%s/ports' % (self.uri_prefix)
+ filter_items = ["%s=%s" % (k, v) for (k, v) in filters.iteritems()]
+ querystring = "&".join(filter_items)
+ if querystring:
+ uri = "%s?%s" % (uri, querystring)
resp, body = self.get(uri, self.headers)
body = json.loads(body)
return resp, body
@@ -223,7 +227,7 @@
body = json.loads(body)
return resp, body
- def update_router(self, router_id, **kwargs):
+ def _update_router(self, router_id, set_enable_snat, **kwargs):
uri = '%s/routers/%s' % (self.uri_prefix, router_id)
resp, body = self.get(uri, self.headers)
body = json.loads(body)
@@ -231,15 +235,34 @@
update_body['name'] = kwargs.get('name', body['router']['name'])
update_body['admin_state_up'] = kwargs.get(
'admin_state_up', body['router']['admin_state_up'])
- # Must uncomment/modify these lines once LP question#233187 is solved
- # update_body['external_gateway_info'] = kwargs.get(
- # 'external_gateway_info', body['router']['external_gateway_info'])
+ cur_gw_info = body['router']['external_gateway_info']
+ if cur_gw_info and not set_enable_snat:
+ cur_gw_info.pop('enable_snat', None)
+ update_body['external_gateway_info'] = kwargs.get(
+ 'external_gateway_info', body['router']['external_gateway_info'])
update_body = dict(router=update_body)
update_body = json.dumps(update_body)
resp, body = self.put(uri, update_body, self.headers)
body = json.loads(body)
return resp, body
+ def update_router(self, router_id, **kwargs):
+ """Update a router leaving enable_snat to its default value."""
+ # If external_gateway_info contains enable_snat the request will fail
+ # with 404 unless executed with admin client, and therefore we instruct
+ # _update_router to not set this attribute
+ # NOTE(salv-orlando): The above applies as long as Neutron's default
+ # policy is to restrict enable_snat usage to admins only.
+ return self._update_router(router_id, set_enable_snat=False, **kwargs)
+
+ def update_router_with_snat_gw_info(self, router_id, **kwargs):
+ """Update a router passing also the enable_snat attribute.
+
+ This method must be execute with admin credentials, otherwise the API
+ call will return a 404 error.
+ """
+ return self._update_router(router_id, set_enable_snat=True, **kwargs)
+
def add_router_interface_with_subnet_id(self, router_id, subnet_id):
uri = '%s/routers/%s/add_router_interface' % (self.uri_prefix,
router_id)
@@ -600,3 +623,77 @@
resp, body = self.get(uri, headers=self.headers)
body = json.loads(body)
return resp, body
+
+ def list_vpn_services(self):
+ uri = '%s/vpn/vpnservices' % (self.uri_prefix)
+ resp, body = self.get(uri, self.headers)
+ body = json.loads(body)
+ return resp, body
+
+ def create_vpn_service(self, subnet_id, router_id, **kwargs):
+ post_body = {
+ "vpnservice": {
+ "subnet_id": subnet_id,
+ "router_id": router_id
+ }
+ }
+ for key, val in kwargs.items():
+ post_body['vpnservice'][key] = val
+ body = json.dumps(post_body)
+ uri = '%s/vpn/vpnservices' % (self.uri_prefix)
+ resp, body = self.post(uri, headers=self.headers, body=body)
+ body = json.loads(body)
+ return resp, body
+
+ def show_vpn_service(self, uuid):
+ uri = '%s/vpn/vpnservices/%s' % (self.uri_prefix, uuid)
+ resp, body = self.get(uri, self.headers)
+ body = json.loads(body)
+ return resp, body
+
+ def delete_vpn_service(self, uuid):
+ uri = '%s/vpn/vpnservices/%s' % (self.uri_prefix, uuid)
+ resp, body = self.delete(uri, self.headers)
+ return resp, body
+
+ def update_vpn_service(self, uuid, description):
+ put_body = {
+ "vpnservice": {
+ "description": description
+ }
+ }
+ body = json.dumps(put_body)
+ uri = '%s/vpn/vpnservices/%s' % (self.uri_prefix, uuid)
+ resp, body = self.put(uri, body=body, headers=self.headers)
+ body = json.loads(body)
+ return resp, body
+
+ def list_router_interfaces(self, uuid):
+ uri = '%s/ports?device_id=%s' % (self.uri_prefix, uuid)
+ resp, body = self.get(uri, self.headers)
+ body = json.loads(body)
+ return resp, body
+
+ def list_agents(self):
+ uri = '%s/agents' % self.uri_prefix
+ resp, body = self.get(uri, self.headers)
+ body = json.loads(body)
+ return resp, body
+
+ def list_routers_on_l3_agent(self, agent_id):
+ uri = '%s/agents/%s/l3-routers' % (self.uri_prefix, agent_id)
+ resp, body = self.get(uri, self.headers)
+ body = json.loads(body)
+ return resp, body
+
+ def list_l3_agents_hosting_router(self, router_id):
+ uri = '%s/routers/%s/l3-agents' % (self.uri_prefix, router_id)
+ resp, body = self.get(uri, self.headers)
+ body = json.loads(body)
+ return resp, body
+
+ def list_service_providers(self):
+ uri = '%s/service-providers' % self.uri_prefix
+ resp, body = self.get(uri, self.headers)
+ body = json.loads(body)
+ return resp, body
diff --git a/tempest/services/network/xml/network_client.py b/tempest/services/network/xml/network_client.py
index cf8154a..5af2dfb 100755
--- a/tempest/services/network/xml/network_client.py
+++ b/tempest/services/network/xml/network_client.py
@@ -547,6 +547,39 @@
body = _root_tag_fetcher_and_xml_to_json_parse(body)
return resp, body
+ def list_router_interfaces(self, uuid):
+ uri = '%s/ports?device_id=%s' % (self.uri_prefix, uuid)
+ resp, body = self.get(uri, self.headers)
+ ports = self._parse_array(etree.fromstring(body))
+ ports = {"ports": ports}
+ return resp, ports
+
+ def list_agents(self):
+ uri = '%s/agents' % self.uri_prefix
+ resp, body = self.get(uri, self.headers)
+ agents = self._parse_array(etree.fromstring(body))
+ agents = {'agents': agents}
+ return resp, agents
+
+ def list_routers_on_l3_agent(self, agent_id):
+ uri = '%s/agents/%s/l3-routers' % (self.uri_prefix, agent_id)
+ resp, body = self.get(uri, self.headers)
+ body = _root_tag_fetcher_and_xml_to_json_parse(body)
+ return resp, body
+
+ def list_l3_agents_hosting_router(self, router_id):
+ uri = '%s/routers/%s/l3-agents' % (self.uri_prefix, router_id)
+ resp, body = self.get(uri, self.headers)
+ body = _root_tag_fetcher_and_xml_to_json_parse(body)
+ return resp, body
+
+ def list_service_providers(self):
+ uri = '%s/service-providers' % self.uri_prefix
+ resp, body = self.get(uri, self.headers)
+ providers = self._parse_array(etree.fromstring(body))
+ body = {'service_providers': providers}
+ return resp, body
+
def _root_tag_fetcher_and_xml_to_json_parse(xml_returned_body):
body = ET.fromstring(xml_returned_body)
diff --git a/tempest/services/volume/json/snapshots_client.py b/tempest/services/volume/json/snapshots_client.py
index 10ba3fd..5d980eb 100644
--- a/tempest/services/volume/json/snapshots_client.py
+++ b/tempest/services/volume/json/snapshots_client.py
@@ -131,3 +131,21 @@
except exceptions.NotFound:
return True
return False
+
+ def reset_snapshot_status(self, snapshot_id, status):
+ """Reset the specified snapshot's status."""
+ post_body = json.dumps({'os-reset_status': {"status": status}})
+ resp, body = self.post('snapshots/%s/action' % snapshot_id, post_body,
+ self.headers)
+ return resp, body
+
+ def update_snapshot_status(self, snapshot_id, status, progress):
+ """Update the specified snapshot's status."""
+ post_body = {
+ 'status': status,
+ 'progress': progress
+ }
+ post_body = json.dumps({'os-update_snapshot_status': post_body})
+ url = 'snapshots/%s/action' % str(snapshot_id)
+ resp, body = self.post(url, post_body, self.headers)
+ return resp, body
diff --git a/tempest/services/volume/json/volumes_client.py b/tempest/services/volume/json/volumes_client.py
index 62a6e24..b4a1a68 100644
--- a/tempest/services/volume/json/volumes_client.py
+++ b/tempest/services/volume/json/volumes_client.py
@@ -128,6 +128,22 @@
resp, body = self.post(url, post_body, self.headers)
return resp, body
+ def reserve_volume(self, volume_id):
+ """Reserves a volume."""
+ post_body = {}
+ post_body = json.dumps({'os-reserve': post_body})
+ url = 'volumes/%s/action' % (volume_id)
+ resp, body = self.post(url, post_body, self.headers)
+ return resp, body
+
+ def unreserve_volume(self, volume_id):
+ """Restore a reserved volume ."""
+ post_body = {}
+ post_body = json.dumps({'os-unreserve': post_body})
+ url = 'volumes/%s/action' % (volume_id)
+ resp, body = self.post(url, post_body, self.headers)
+ return resp, body
+
def wait_for_volume_status(self, volume_id, status):
"""Waits for a Volume to reach a given status."""
resp, body = self.get_volume(volume_id)
@@ -154,3 +170,89 @@
except exceptions.NotFound:
return True
return False
+
+ def extend_volume(self, volume_id, extend_size):
+ """Extend a volume."""
+ post_body = {
+ 'new_size': extend_size
+ }
+ post_body = json.dumps({'os-extend': post_body})
+ url = 'volumes/%s/action' % (volume_id)
+ resp, body = self.post(url, post_body, self.headers)
+ return resp, body
+
+ def reset_volume_status(self, volume_id, status):
+ """Reset the Specified Volume's Status."""
+ post_body = json.dumps({'os-reset_status': {"status": status}})
+ resp, body = self.post('volumes/%s/action' % volume_id, post_body,
+ self.headers)
+ return resp, body
+
+ def volume_begin_detaching(self, volume_id):
+ """Volume Begin Detaching."""
+ post_body = json.dumps({'os-begin_detaching': {}})
+ resp, body = self.post('volumes/%s/action' % volume_id, post_body,
+ self.headers)
+ return resp, body
+
+ def volume_roll_detaching(self, volume_id):
+ """Volume Roll Detaching."""
+ post_body = json.dumps({'os-roll_detaching': {}})
+ resp, body = self.post('volumes/%s/action' % volume_id, post_body,
+ self.headers)
+ return resp, body
+
+ def create_volume_transfer(self, vol_id, display_name=None):
+ """Create a volume transfer."""
+ post_body = {
+ 'volume_id': vol_id
+ }
+ if display_name:
+ post_body['name'] = display_name
+ post_body = json.dumps({'transfer': post_body})
+ resp, body = self.post('os-volume-transfer',
+ post_body,
+ self.headers)
+ body = json.loads(body)
+ return resp, body['transfer']
+
+ def get_volume_transfer(self, transfer_id):
+ """Returns the details of a volume transfer."""
+ url = "os-volume-transfer/%s" % str(transfer_id)
+ resp, body = self.get(url, self.headers)
+ body = json.loads(body)
+ return resp, body['transfer']
+
+ def list_volume_transfers(self, params=None):
+ """List all the volume transfers created."""
+ url = 'os-volume-transfer'
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+ resp, body = self.get(url)
+ body = json.loads(body)
+ return resp, body['transfers']
+
+ def delete_volume_transfer(self, transfer_id):
+ """Delete a volume transfer."""
+ return self.delete("os-volume-transfer/%s" % str(transfer_id))
+
+ def accept_volume_transfer(self, transfer_id, transfer_auth_key):
+ """Accept a volume transfer."""
+ post_body = {
+ 'auth_key': transfer_auth_key,
+ }
+ url = 'os-volume-transfer/%s/accept' % transfer_id
+ post_body = json.dumps({'accept': post_body})
+ resp, body = self.post(url, post_body, self.headers)
+ body = json.loads(body)
+ return resp, body['transfer']
+
+ def update_volume_readonly(self, volume_id, readonly):
+ """Update the Specified Volume readonly."""
+ post_body = {
+ 'readonly': readonly
+ }
+ post_body = json.dumps({'os-update_readonly_flag': post_body})
+ url = 'volumes/%s/action' % (volume_id)
+ resp, body = self.post(url, post_body, self.headers)
+ return resp, body
diff --git a/tempest/services/volume/xml/snapshots_client.py b/tempest/services/volume/xml/snapshots_client.py
index b7ba56b..5d59b07 100644
--- a/tempest/services/volume/xml/snapshots_client.py
+++ b/tempest/services/volume/xml/snapshots_client.py
@@ -147,3 +147,26 @@
except exceptions.NotFound:
return True
return False
+
+ def reset_snapshot_status(self, snapshot_id, status):
+ """Reset the specified snapshot's status."""
+ post_body = Element("os-reset_status",
+ status=status
+ )
+ url = 'snapshots/%s/action' % str(snapshot_id)
+ resp, body = self.post(url, str(Document(post_body)), self.headers)
+ if body:
+ body = xml_to_json(etree.fromstring(body))
+ return resp, body
+
+ def update_snapshot_status(self, snapshot_id, status, progress):
+ """Update the specified snapshot's status."""
+ post_body = Element("os-update_snapshot_status",
+ status=status,
+ progress=progress
+ )
+ url = 'snapshots/%s/action' % str(snapshot_id)
+ resp, body = self.post(url, str(Document(post_body)), self.headers)
+ if body:
+ body = xml_to_json(etree.fromstring(body))
+ return resp, body
diff --git a/tempest/services/volume/xml/volumes_client.py b/tempest/services/volume/xml/volumes_client.py
index b59ec03..21254aa 100644
--- a/tempest/services/volume/xml/volumes_client.py
+++ b/tempest/services/volume/xml/volumes_client.py
@@ -227,3 +227,123 @@
resp, body = self.post(url, str(Document(post_body)), self.headers)
volume = xml_to_json(etree.fromstring(body))
return resp, volume
+
+ def extend_volume(self, volume_id, extend_size):
+ """Extend a volume."""
+ post_body = Element("os-extend",
+ new_size=extend_size)
+ url = 'volumes/%s/action' % str(volume_id)
+ resp, body = self.post(url, str(Document(post_body)), self.headers)
+ if body:
+ body = xml_to_json(etree.fromstring(body))
+ return resp, body
+
+ def reset_volume_status(self, volume_id, status):
+ """Reset the Specified Volume's Status."""
+ post_body = Element("os-reset_status",
+ status=status
+ )
+ url = 'volumes/%s/action' % str(volume_id)
+ resp, body = self.post(url, str(Document(post_body)), self.headers)
+ if body:
+ body = xml_to_json(etree.fromstring(body))
+ return resp, body
+
+ def volume_begin_detaching(self, volume_id):
+ """Volume Begin Detaching."""
+ post_body = Element("os-begin_detaching")
+ url = 'volumes/%s/action' % str(volume_id)
+ resp, body = self.post(url, str(Document(post_body)), self.headers)
+ if body:
+ body = xml_to_json(etree.fromstring(body))
+ return resp, body
+
+ def volume_roll_detaching(self, volume_id):
+ """Volume Roll Detaching."""
+ post_body = Element("os-roll_detaching")
+ url = 'volumes/%s/action' % str(volume_id)
+ resp, body = self.post(url, str(Document(post_body)), self.headers)
+ if body:
+ body = xml_to_json(etree.fromstring(body))
+ return resp, body
+
+ def reserve_volume(self, volume_id):
+ """Reserves a volume."""
+ post_body = Element("os-reserve")
+ url = 'volumes/%s/action' % str(volume_id)
+ resp, body = self.post(url, str(Document(post_body)), self.headers)
+ if body:
+ body = xml_to_json(etree.fromstring(body))
+ return resp, body
+
+ def unreserve_volume(self, volume_id):
+ """Restore a reserved volume ."""
+ post_body = Element("os-unreserve")
+ url = 'volumes/%s/action' % str(volume_id)
+ resp, body = self.post(url, str(Document(post_body)), self.headers)
+ if body:
+ body = xml_to_json(etree.fromstring(body))
+ return resp, body
+
+ def create_volume_transfer(self, vol_id, display_name=None):
+ """Create a volume transfer."""
+ post_body = Element("transfer",
+ volume_id=vol_id)
+ if display_name:
+ post_body.add_attr('name', display_name)
+ resp, body = self.post('os-volume-transfer',
+ str(Document(post_body)),
+ self.headers)
+ volume = xml_to_json(etree.fromstring(body))
+ return resp, volume
+
+ def get_volume_transfer(self, transfer_id):
+ """Returns the details of a volume transfer."""
+ url = "os-volume-transfer/%s" % str(transfer_id)
+ resp, body = self.get(url, self.headers)
+ volume = xml_to_json(etree.fromstring(body))
+ return resp, volume
+
+ def list_volume_transfers(self, params=None):
+ """List all the volume transfers created."""
+ url = 'os-volume-transfer'
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+
+ resp, body = self.get(url, self.headers)
+ body = etree.fromstring(body)
+ volumes = []
+ if body is not None:
+ volumes += [self._parse_volume_transfer(vol) for vol in list(body)]
+ return resp, volumes
+
+ def _parse_volume_transfer(self, body):
+ vol = dict((attr, body.get(attr)) for attr in body.keys())
+ for child in body.getchildren():
+ tag = child.tag
+ if tag.startswith("{"):
+ tag = tag.split("}", 1)
+ vol[tag] = xml_to_json(child)
+ return vol
+
+ def delete_volume_transfer(self, transfer_id):
+ """Delete a volume transfer."""
+ return self.delete("os-volume-transfer/%s" % str(transfer_id))
+
+ def accept_volume_transfer(self, transfer_id, transfer_auth_key):
+ """Accept a volume transfer."""
+ post_body = Element("accept", auth_key=transfer_auth_key)
+ url = 'os-volume-transfer/%s/accept' % transfer_id
+ resp, body = self.post(url, str(Document(post_body)), self.headers)
+ volume = xml_to_json(etree.fromstring(body))
+ return resp, volume
+
+ def update_volume_readonly(self, volume_id, readonly):
+ """Update the Specified Volume readonly."""
+ post_body = Element("os-update_readonly_flag",
+ readonly=readonly)
+ url = 'volumes/%s/action' % str(volume_id)
+ resp, body = self.post(url, str(Document(post_body)), self.headers)
+ if body:
+ body = xml_to_json(etree.fromstring(body))
+ return resp, body
diff --git a/tempest/stress/README.rst b/tempest/stress/README.rst
index ae86f6e..20e58d4 100644
--- a/tempest/stress/README.rst
+++ b/tempest/stress/README.rst
@@ -26,6 +26,16 @@
To activate logging on your console please make sure that you activate `use_stderr`
in tempest.conf or use the default `logging.conf.sample` file.
+Running default stress test set
+-------------------------------
+
+The stress test framework can automatically discover test inside the tempest
+test suite. All test flag with the `@stresstest` decorator will be executed.
+In order to use this discovery you have to be in the tempest root directory
+and execute the following:
+
+ tempest/stress/run_stress.py -a -d 30
+
Running the sample test
-----------------------
diff --git a/tempest/stress/actions/server_create_destroy.py b/tempest/stress/actions/server_create_destroy.py
index 1a1e30b..84c7cf5 100644
--- a/tempest/stress/actions/server_create_destroy.py
+++ b/tempest/stress/actions/server_create_destroy.py
@@ -12,7 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-from tempest.common.utils.data_utils import rand_name
+from tempest.common.utils import data_utils
import tempest.stress.stressaction as stressaction
@@ -23,7 +23,7 @@
self.flavor = self.manager.config.compute.flavor_ref
def run(self):
- name = rand_name("instance")
+ name = data_utils.rand_name("instance")
self.logger.info("creating %s" % name)
resp, server = self.manager.servers_client.create_server(
name, self.image, self.flavor)
diff --git a/tempest/stress/actions/ssh_floating.py b/tempest/stress/actions/ssh_floating.py
index 36ef023..74a9739 100644
--- a/tempest/stress/actions/ssh_floating.py
+++ b/tempest/stress/actions/ssh_floating.py
@@ -15,7 +15,7 @@
import socket
import subprocess
-from tempest.common.utils.data_utils import rand_name
+from tempest.common.utils import data_utils
import tempest.stress.stressaction as stressaction
import tempest.test
@@ -64,7 +64,7 @@
raise RuntimeError("Cannot ping the machine.")
def _create_vm(self):
- self.name = name = rand_name("instance")
+ self.name = name = data_utils.rand_name("instance")
servers_client = self.manager.servers_client
self.logger.info("creating %s" % name)
vm_args = self.vm_extra_args.copy()
@@ -87,8 +87,8 @@
def _create_sec_group(self):
sec_grp_cli = self.manager.security_groups_client
- s_name = rand_name('sec_grp-')
- s_description = rand_name('desc-')
+ s_name = data_utils.rand_name('sec_grp-')
+ s_description = data_utils.rand_name('desc-')
_, _sec_grp = sec_grp_cli.create_security_group(s_name,
s_description)
self.sec_grp = _sec_grp['id']
diff --git a/tempest/stress/actions/volume_attach_delete.py b/tempest/stress/actions/volume_attach_delete.py
index a7b872f..e6fcb81 100644
--- a/tempest/stress/actions/volume_attach_delete.py
+++ b/tempest/stress/actions/volume_attach_delete.py
@@ -11,7 +11,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-from tempest.common.utils.data_utils import rand_name
+from tempest.common.utils import data_utils
import tempest.stress.stressaction as stressaction
@@ -23,7 +23,7 @@
def run(self):
# Step 1: create volume
- name = rand_name("volume")
+ name = data_utils.rand_name("volume")
self.logger.info("creating volume: %s" % name)
resp, volume = self.manager.volumes_client.create_volume(size=1,
display_name=
@@ -34,7 +34,7 @@
self.logger.info("created volume: %s" % volume['id'])
# Step 2: create vm instance
- vm_name = rand_name("instance")
+ vm_name = data_utils.rand_name("instance")
self.logger.info("creating vm: %s" % vm_name)
resp, server = self.manager.servers_client.create_server(
vm_name, self.image, self.flavor)
diff --git a/tempest/stress/actions/volume_create_delete.py b/tempest/stress/actions/volume_create_delete.py
index e29d9c4..4e75be0 100644
--- a/tempest/stress/actions/volume_create_delete.py
+++ b/tempest/stress/actions/volume_create_delete.py
@@ -10,14 +10,14 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-from tempest.common.utils.data_utils import rand_name
+from tempest.common.utils import data_utils
import tempest.stress.stressaction as stressaction
class VolumeCreateDeleteTest(stressaction.StressAction):
def run(self):
- name = rand_name("volume")
+ name = data_utils.rand_name("volume")
self.logger.info("creating %s" % name)
volumes_client = self.manager.volumes_client
resp, volume = volumes_client.create_volume(size=1,
diff --git a/tempest/stress/driver.py b/tempest/stress/driver.py
index b5cab68..d37ab6d 100644
--- a/tempest/stress/driver.py
+++ b/tempest/stress/driver.py
@@ -19,25 +19,18 @@
from tempest import clients
from tempest.common import ssh
-from tempest.common.utils.data_utils import rand_name
+from tempest.common.utils import data_utils
from tempest import exceptions
from tempest.openstack.common import importutils
from tempest.openstack.common import log as logging
from tempest.stress import cleanup
-admin_manager = clients.AdminManager()
-
LOG = logging.getLogger(__name__)
processes = []
-def do_ssh(command, host):
- username = admin_manager.config.stress.target_ssh_user
- key_filename = admin_manager.config.stress.target_private_key_path
- if not (username and key_filename):
- LOG.error('username and key_filename should not be empty')
- return None
- ssh_client = ssh.Client(host, username, key_filename=key_filename)
+def do_ssh(command, host, ssh_user, ssh_key=None):
+ ssh_client = ssh.Client(host, ssh_user, key_filename=ssh_key)
try:
return ssh_client.exec_command(command)
except exceptions.SSHExecCommandFailed:
@@ -46,14 +39,14 @@
return None
-def _get_compute_nodes(controller):
+def _get_compute_nodes(controller, ssh_user, ssh_key=None):
"""
Returns a list of active compute nodes. List is generated by running
nova-manage on the controller.
"""
nodes = []
cmd = "nova-manage service list | grep ^nova-compute"
- output = do_ssh(cmd, controller)
+ output = do_ssh(cmd, controller, ssh_user, ssh_key)
if not output:
return nodes
# For example: nova-compute xg11eth0 nova enabled :-) 2011-10-31 18:57:46
@@ -65,14 +58,15 @@
return nodes
-def _has_error_in_logs(logfiles, nodes, stop_on_error=False):
+def _has_error_in_logs(logfiles, nodes, ssh_user, ssh_key=None,
+ stop_on_error=False):
"""
Detect errors in the nova log files on the controller and compute nodes.
"""
grep = 'egrep "ERROR|TRACE" %s' % logfiles
ret = False
for node in nodes:
- errors = do_ssh(grep, node)
+ errors = do_ssh(grep, node, ssh_user, ssh_key)
if len(errors) > 0:
LOG.error('%s: %s' % (node, errors))
ret = True
@@ -88,18 +82,17 @@
terminate_all_processes()
-def terminate_all_processes():
+def terminate_all_processes(check_interval=20):
"""
Goes through the process list and terminates all child processes.
"""
- log_check_interval = int(admin_manager.config.stress.log_check_interval)
for process in processes:
if process['process'].is_alive():
try:
process['process'].terminate()
except Exception:
pass
- time.sleep(log_check_interval)
+ time.sleep(check_interval)
for process in processes:
if process['process'].is_alive():
try:
@@ -115,15 +108,19 @@
"""
Workload driver. Executes an action function against a nova-cluster.
"""
+ admin_manager = clients.AdminManager()
+
+ ssh_user = admin_manager.config.stress.target_ssh_user
+ ssh_key = admin_manager.config.stress.target_private_key_path
logfiles = admin_manager.config.stress.target_logfiles
log_check_interval = int(admin_manager.config.stress.log_check_interval)
default_thread_num = int(admin_manager.config.stress.
default_thread_number_per_action)
if logfiles:
controller = admin_manager.config.stress.target_controller
- computes = _get_compute_nodes(controller)
+ computes = _get_compute_nodes(controller, ssh_user, ssh_key)
for node in computes:
- do_ssh("rm -f %s" % logfiles, node)
+ do_ssh("rm -f %s" % logfiles, node, ssh_user, ssh_key)
for test in tests:
if test.get('use_admin', False):
manager = admin_manager
@@ -131,8 +128,8 @@
manager = clients.Manager()
for p_number in xrange(test.get('threads', default_thread_num)):
if test.get('use_isolated_tenants', False):
- username = rand_name("stress_user")
- tenant_name = rand_name("stress_tenant")
+ username = data_utils.rand_name("stress_user")
+ tenant_name = data_utils.rand_name("stress_tenant")
password = "pass"
identity_client = admin_manager.identity_client
_, tenant = identity_client.create_tenant(name=tenant_name)
@@ -196,7 +193,8 @@
if not logfiles:
continue
- if _has_error_in_logs(logfiles, computes, stop_on_error):
+ if _has_error_in_logs(logfiles, computes, ssh_user, ssh_key,
+ stop_on_error):
had_errors = True
break
diff --git a/tempest/test.py b/tempest/test.py
index 8ce7af8..6ae7925 100644
--- a/tempest/test.py
+++ b/tempest/test.py
@@ -26,6 +26,7 @@
import testtools
from tempest import clients
+from tempest.common import isolated_creds
from tempest import config
from tempest import exceptions
from tempest.openstack.common import log as logging
@@ -164,7 +165,11 @@
if at_exit_set:
raise RuntimeError("tearDownClass does not calls the super's "
"tearDownClass in these classes: "
- + str(at_exit_set))
+ + str(at_exit_set) + "\n"
+ "If you see the exception, with another "
+ "exception please do not report this one!"
+ "If you are changing tempest code, make sure you",
+ "are calling the super class's tearDownClass!")
atexit.register(validate_tearDownClass)
@@ -216,7 +221,36 @@
os.environ.get('OS_LOG_CAPTURE') != '0'):
log_format = '%(asctime)-15s %(message)s'
self.useFixture(fixtures.LoggerFixture(nuke_handlers=False,
- format=log_format))
+ format=log_format,
+ level=None))
+
+ @classmethod
+ def get_client_manager(cls):
+ """
+ Returns an Openstack client manager
+ """
+ cls.isolated_creds = isolated_creds.IsolatedCreds(cls.__name__)
+
+ force_tenant_isolation = getattr(cls, 'force_tenant_isolation', None)
+ if (cls.config.compute.allow_tenant_isolation or
+ force_tenant_isolation):
+ creds = cls.isolated_creds.get_primary_creds()
+ username, tenant_name, password = creds
+ os = clients.Manager(username=username,
+ password=password,
+ tenant_name=tenant_name,
+ interface=cls._interface)
+ else:
+ os = clients.Manager(interface=cls._interface)
+ return os
+
+ @classmethod
+ def clear_isolated_creds(cls):
+ """
+ Clears isolated creds if set
+ """
+ if getattr(cls, 'isolated_creds'):
+ cls.isolated_creds.clear_isolated_creds()
@classmethod
def _get_identity_admin_client(cls):
diff --git a/tempest/tests/base.py b/tempest/tests/base.py
new file mode 100644
index 0000000..ba83cf4
--- /dev/null
+++ b/tempest/tests/base.py
@@ -0,0 +1,40 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 IBM Corp.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import os
+
+import fixtures
+import testtools
+
+from tempest.openstack.common.fixture import moxstubout
+
+
+class TestCase(testtools.TestCase):
+
+ def setUp(self):
+ super(TestCase, self).setUp()
+ if (os.environ.get('OS_STDOUT_CAPTURE') == 'True' or
+ os.environ.get('OS_STDOUT_CAPTURE') == '1'):
+ stdout = self.useFixture(fixtures.StringStream('stdout')).stream
+ self.useFixture(fixtures.MonkeyPatch('sys.stdout', stdout))
+ if (os.environ.get('OS_STDERR_CAPTURE') == 'True' or
+ os.environ.get('OS_STDERR_CAPTURE') == '1'):
+ stderr = self.useFixture(fixtures.StringStream('stderr')).stream
+ self.useFixture(fixtures.MonkeyPatch('sys.stderr', stderr))
+
+ mox_fixture = self.useFixture(moxstubout.MoxStubout())
+ self.mox = mox_fixture.mox
+ self.stubs = mox_fixture.stubs
diff --git a/tempest/tests/test_list_tests.py b/tempest/tests/test_list_tests.py
new file mode 100644
index 0000000..ab0d114
--- /dev/null
+++ b/tempest/tests/test_list_tests.py
@@ -0,0 +1,38 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 IBM Corp.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import re
+import subprocess
+
+from tempest.tests import base
+
+
+class TestTestList(base.TestCase):
+
+ def test_no_import_errors(self):
+ import_failures = []
+ p = subprocess.Popen(['testr', 'list-tests'], stdout=subprocess.PIPE)
+ ids = p.stdout.read()
+ ids = ids.split('\n')
+ for test_id in ids:
+ if re.match('(\w+\.){3}\w+', test_id):
+ if not test_id.startswith('tempest.'):
+ fail_id = test_id.split('unittest.loader.ModuleImport'
+ 'Failure.')[1]
+ import_failures.append(fail_id)
+ error_message = ("The following tests have import failures and aren't"
+ " being run with test filters %s" % import_failures)
+ self.assertFalse(import_failures, error_message)
diff --git a/tempest/tests/test_wrappers.py b/tempest/tests/test_wrappers.py
index 1a5af00..dbf1809 100644
--- a/tempest/tests/test_wrappers.py
+++ b/tempest/tests/test_wrappers.py
@@ -18,14 +18,13 @@
import shutil
import subprocess
import tempfile
-import testtools
-from tempest.test import attr
+from tempest.tests import base
DEVNULL = open(os.devnull, 'wb')
-class TestWrappers(testtools.TestCase):
+class TestWrappers(base.TestCase):
def setUp(self):
super(TestWrappers, self).setUp()
# Setup test dirs
@@ -46,7 +45,6 @@
shutil.copy('tempest/tests/files/setup.cfg', self.setup_cfg_file)
shutil.copy('tempest/tests/files/__init__.py', self.init_file)
- @attr(type='smoke')
def test_pretty_tox(self):
# Copy wrapper script and requirements:
pretty_tox = os.path.join(self.directory, 'pretty_tox.sh')
@@ -62,7 +60,6 @@
shell=True, stdout=DEVNULL, stderr=DEVNULL)
self.assertEqual(exit_code, 0)
- @attr(type='smoke')
def test_pretty_tox_fails(self):
# Copy wrapper script and requirements:
pretty_tox = os.path.join(self.directory, 'pretty_tox.sh')
@@ -78,7 +75,6 @@
stdout=DEVNULL, stderr=DEVNULL)
self.assertEqual(exit_code, 1)
- @attr(type='smoke')
def test_pretty_tox_serial(self):
# Copy wrapper script and requirements:
pretty_tox = os.path.join(self.directory, 'pretty_tox_serial.sh')
@@ -90,7 +86,6 @@
shell=True, stdout=DEVNULL, stderr=DEVNULL)
self.assertEqual(exit_code, 0)
- @attr(type='smoke')
def test_pretty_tox_serial_fails(self):
# Copy wrapper script and requirements:
pretty_tox = os.path.join(self.directory, 'pretty_tox_serial.sh')
diff --git a/tempest/thirdparty/boto/test.py b/tempest/thirdparty/boto/test.py
index 5295e44..5ae21c8 100644
--- a/tempest/thirdparty/boto/test.py
+++ b/tempest/thirdparty/boto/test.py
@@ -193,11 +193,10 @@
class BotoTestCase(tempest.test.BaseTestCase):
"""Recommended to use as base class for boto related test."""
- conclusion = decision_maker()
-
@classmethod
def setUpClass(cls):
super(BotoTestCase, cls).setUpClass()
+ cls.conclusion = decision_maker()
# The trash contains cleanup functions and paramaters in tuples
# (function, *args, **kwargs)
cls._resource_trash_bin = {}
diff --git a/tempest/thirdparty/boto/test_ec2_instance_run.py b/tempest/thirdparty/boto/test_ec2_instance_run.py
index 0f455e1..9ded9da 100644
--- a/tempest/thirdparty/boto/test_ec2_instance_run.py
+++ b/tempest/thirdparty/boto/test_ec2_instance_run.py
@@ -18,7 +18,7 @@
from boto import exception
from tempest import clients
-from tempest.common.utils.data_utils import rand_name
+from tempest.common.utils import data_utils
from tempest.common.utils.linux.remote_client import RemoteClient
from tempest import exceptions
from tempest.openstack.common import log as logging
@@ -50,8 +50,8 @@
aki_manifest = config.boto.aki_manifest
ari_manifest = config.boto.ari_manifest
cls.instance_type = config.boto.instance_type
- cls.bucket_name = rand_name("s3bucket-")
- cls.keypair_name = rand_name("keypair-")
+ cls.bucket_name = data_utils.rand_name("s3bucket-")
+ cls.keypair_name = data_utils.rand_name("keypair-")
cls.keypair = cls.ec2_client.create_key_pair(cls.keypair_name)
cls.addResourceCleanUp(cls.ec2_client.delete_key_pair,
cls.keypair_name)
@@ -61,13 +61,13 @@
cls.bucket_name)
s3_upload_dir(bucket, cls.materials_path)
cls.images = {"ami":
- {"name": rand_name("ami-name-"),
+ {"name": data_utils.rand_name("ami-name-"),
"location": cls.bucket_name + "/" + ami_manifest},
"aki":
- {"name": rand_name("aki-name-"),
+ {"name": data_utils.rand_name("aki-name-"),
"location": cls.bucket_name + "/" + aki_manifest},
"ari":
- {"name": rand_name("ari-name-"),
+ {"name": data_utils.rand_name("ari-name-"),
"location": cls.bucket_name + "/" + ari_manifest}}
for image in cls.images.itervalues():
image["image_id"] = cls.ec2_client.register_image(
@@ -238,7 +238,7 @@
def test_integration_1(self):
# EC2 1. integration test (not strict)
image_ami = self.ec2_client.get_image(self.images["ami"]["image_id"])
- sec_group_name = rand_name("securitygroup-")
+ sec_group_name = data_utils.rand_name("securitygroup-")
group_desc = sec_group_name + " security group description "
security_group = self.ec2_client.create_security_group(sec_group_name,
group_desc)
@@ -285,7 +285,7 @@
ssh = RemoteClient(address.public_ip,
self.os.config.compute.ssh_user,
pkey=self.keypair.material)
- text = rand_name("Pattern text for console output -")
+ text = data_utils.rand_name("Pattern text for console output -")
resp = ssh.write_to_console(text)
self.assertFalse(resp)
diff --git a/tempest/thirdparty/boto/test_ec2_keys.py b/tempest/thirdparty/boto/test_ec2_keys.py
index 5592d8c..41db709 100644
--- a/tempest/thirdparty/boto/test_ec2_keys.py
+++ b/tempest/thirdparty/boto/test_ec2_keys.py
@@ -16,7 +16,7 @@
# under the License.
from tempest import clients
-from tempest.common.utils.data_utils import rand_name
+from tempest.common.utils import data_utils
from tempest.test import attr
from tempest.test import skip_because
from tempest.thirdparty.boto.test import BotoTestCase
@@ -40,7 +40,7 @@
@attr(type='smoke')
def test_create_ec2_keypair(self):
# EC2 create KeyPair
- key_name = rand_name("keypair-")
+ key_name = data_utils.rand_name("keypair-")
self.addResourceCleanUp(self.client.delete_key_pair, key_name)
keypair = self.client.create_key_pair(key_name)
self.assertTrue(compare_key_pairs(keypair,
@@ -50,7 +50,7 @@
@attr(type='smoke')
def test_delete_ec2_keypair(self):
# EC2 delete KeyPair
- key_name = rand_name("keypair-")
+ key_name = data_utils.rand_name("keypair-")
self.client.create_key_pair(key_name)
self.client.delete_key_pair(key_name)
self.assertEqual(None, self.client.get_key_pair(key_name))
@@ -58,7 +58,7 @@
@attr(type='smoke')
def test_get_ec2_keypair(self):
# EC2 get KeyPair
- key_name = rand_name("keypair-")
+ key_name = data_utils.rand_name("keypair-")
self.addResourceCleanUp(self.client.delete_key_pair, key_name)
keypair = self.client.create_key_pair(key_name)
self.assertTrue(compare_key_pairs(keypair,
@@ -67,7 +67,7 @@
@attr(type='smoke')
def test_duplicate_ec2_keypair(self):
# EC2 duplicate KeyPair
- key_name = rand_name("keypair-")
+ key_name = data_utils.rand_name("keypair-")
self.addResourceCleanUp(self.client.delete_key_pair, key_name)
keypair = self.client.create_key_pair(key_name)
self.assertBotoError(self.ec.client.InvalidKeyPair.Duplicate,
diff --git a/tempest/thirdparty/boto/test_ec2_security_groups.py b/tempest/thirdparty/boto/test_ec2_security_groups.py
index 3b10cfa..e8c6466 100644
--- a/tempest/thirdparty/boto/test_ec2_security_groups.py
+++ b/tempest/thirdparty/boto/test_ec2_security_groups.py
@@ -16,7 +16,7 @@
# under the License.
from tempest import clients
-from tempest.common.utils.data_utils import rand_name
+from tempest.common.utils import data_utils
from tempest.test import attr
from tempest.thirdparty.boto.test import BotoTestCase
@@ -32,7 +32,7 @@
@attr(type='smoke')
def test_create_authorize_security_group(self):
# EC2 Create, authorize/revoke security group
- group_name = rand_name("securty_group-")
+ group_name = data_utils.rand_name("securty_group-")
group_description = group_name + " security group description "
group = self.client.create_security_group(group_name,
group_description)
diff --git a/tempest/thirdparty/boto/test_s3_buckets.py b/tempest/thirdparty/boto/test_s3_buckets.py
index 1a8fbe0..56ee9e3 100644
--- a/tempest/thirdparty/boto/test_s3_buckets.py
+++ b/tempest/thirdparty/boto/test_s3_buckets.py
@@ -16,7 +16,7 @@
# under the License.
from tempest import clients
-from tempest.common.utils.data_utils import rand_name
+from tempest.common.utils import data_utils
from tempest.test import attr
from tempest.test import skip_because
from tempest.thirdparty.boto.test import BotoTestCase
@@ -34,7 +34,7 @@
@attr(type='smoke')
def test_create_and_get_delete_bucket(self):
# S3 Create, get and delete bucket
- bucket_name = rand_name("s3bucket-")
+ bucket_name = data_utils.rand_name("s3bucket-")
cleanup_key = self.addResourceCleanUp(self.client.delete_bucket,
bucket_name)
bucket = self.client.create_bucket(bucket_name)
diff --git a/tempest/thirdparty/boto/test_s3_ec2_images.py b/tempest/thirdparty/boto/test_s3_ec2_images.py
index aaf2569..2e7525a 100644
--- a/tempest/thirdparty/boto/test_s3_ec2_images.py
+++ b/tempest/thirdparty/boto/test_s3_ec2_images.py
@@ -18,7 +18,7 @@
import os
from tempest import clients
-from tempest.common.utils.data_utils import rand_name
+from tempest.common.utils import data_utils
from tempest.test import attr
from tempest.thirdparty.boto.test import BotoTestCase
from tempest.thirdparty.boto.utils.s3 import s3_upload_dir
@@ -43,7 +43,7 @@
cls.ami_path = cls.materials_path + os.sep + cls.ami_manifest
cls.aki_path = cls.materials_path + os.sep + cls.aki_manifest
cls.ari_path = cls.materials_path + os.sep + cls.ari_manifest
- cls.bucket_name = rand_name("bucket-")
+ cls.bucket_name = data_utils.rand_name("bucket-")
bucket = cls.s3_client.create_bucket(cls.bucket_name)
cls.addResourceCleanUp(cls.destroy_bucket,
cls.s3_client.connection_data,
@@ -53,7 +53,7 @@
@attr(type='smoke')
def test_register_get_deregister_ami_image(self):
# Register and deregister ami image
- image = {"name": rand_name("ami-name-"),
+ image = {"name": data_utils.rand_name("ami-name-"),
"location": self.bucket_name + "/" + self.ami_manifest,
"type": "ami"}
image["image_id"] = self.images_client.register_image(
@@ -76,7 +76,7 @@
def test_register_get_deregister_aki_image(self):
# Register and deregister aki image
- image = {"name": rand_name("aki-name-"),
+ image = {"name": data_utils.rand_name("aki-name-"),
"location": self.bucket_name + "/" + self.aki_manifest,
"type": "aki"}
image["image_id"] = self.images_client.register_image(
@@ -99,7 +99,7 @@
def test_register_get_deregister_ari_image(self):
# Register and deregister ari image
- image = {"name": rand_name("ari-name-"),
+ image = {"name": data_utils.rand_name("ari-name-"),
"location": "/" + self.bucket_name + "/" + self.ari_manifest,
"type": "ari"}
image["image_id"] = self.images_client.register_image(
diff --git a/tempest/thirdparty/boto/test_s3_objects.py b/tempest/thirdparty/boto/test_s3_objects.py
index 188d1db..57ec34a 100644
--- a/tempest/thirdparty/boto/test_s3_objects.py
+++ b/tempest/thirdparty/boto/test_s3_objects.py
@@ -20,7 +20,7 @@
import boto.s3.key
from tempest import clients
-from tempest.common.utils.data_utils import rand_name
+from tempest.common.utils import data_utils
from tempest.test import attr
from tempest.thirdparty.boto.test import BotoTestCase
@@ -36,8 +36,8 @@
@attr(type='smoke')
def test_create_get_delete_object(self):
# S3 Create, get and delete object
- bucket_name = rand_name("s3bucket-")
- object_name = rand_name("s3object-")
+ bucket_name = data_utils.rand_name("s3bucket-")
+ object_name = data_utils.rand_name("s3object-")
content = 'x' * 42
bucket = self.client.create_bucket(bucket_name)
self.addResourceCleanUp(self.destroy_bucket,
diff --git a/test-requirements.txt b/test-requirements.txt
index 8aa6ed9..fbe7e43 100644
--- a/test-requirements.txt
+++ b/test-requirements.txt
@@ -4,3 +4,4 @@
sphinx>=1.1.2
python-subunit
oslo.sphinx
+mox>=0.5.3
diff --git a/tools/check_logs.py b/tools/check_logs.py
index 0cc3677..68ffced 100755
--- a/tools/check_logs.py
+++ b/tools/check_logs.py
@@ -16,7 +16,150 @@
# License for the specific language governing permissions and limitations
# under the License.
+import argparse
+import gzip
+import os
+import re
+import StringIO
import sys
+import urllib2
+import yaml
+
+
+is_neutron = os.environ.get('DEVSTACK_GATE_NEUTRON', "0") == "1"
+dump_all_errors = is_neutron
+
+
+def process_files(file_specs, url_specs, whitelists):
+ regexp = re.compile(r"^.*(ERROR|CRITICAL).*\[.*\-.*\]")
+ had_errors = False
+ for (name, filename) in file_specs:
+ whitelist = whitelists.get(name, [])
+ with open(filename) as content:
+ if scan_content(name, content, regexp, whitelist):
+ had_errors = True
+ for (name, url) in url_specs:
+ whitelist = whitelists.get(name, [])
+ req = urllib2.Request(url)
+ req.add_header('Accept-Encoding', 'gzip')
+ page = urllib2.urlopen(req)
+ buf = StringIO.StringIO(page.read())
+ f = gzip.GzipFile(fileobj=buf)
+ if scan_content(name, f.read().splitlines(), regexp, whitelist):
+ had_errors = True
+ return had_errors
+
+
+def scan_content(name, content, regexp, whitelist):
+ had_errors = False
+ print_log_name = True
+ for line in content:
+ if not line.startswith("Stderr:") and regexp.match(line):
+ whitelisted = False
+ for w in whitelist:
+ pat = ".*%s.*%s.*" % (w['module'].replace('.', '\\.'),
+ w['message'])
+ if re.match(pat, line):
+ whitelisted = True
+ break
+ if not whitelisted or dump_all_errors:
+ if not print_log_name:
+ print("Log File: %s" % name)
+ print_log_name = False
+ if not whitelisted:
+ had_errors = True
+ print(line)
+ return had_errors
+
+
+def collect_url_logs(url):
+ page = urllib2.urlopen(url)
+ content = page.read()
+ logs = re.findall('(screen-[\w-]+\.txt\.gz)</a>', content)
+ return logs
+
+
+def main(opts):
+ if opts.directory and opts.url or not (opts.directory or opts.url):
+ print("Must provide exactly one of -d or -u")
+ exit(1)
+ print("Checking logs...")
+ WHITELIST_FILE = os.path.join(
+ os.path.abspath(os.path.dirname(os.path.dirname(__file__))),
+ "etc", "whitelist.yaml")
+
+ file_matcher = re.compile(r".*screen-([\w-]+)\.log")
+ files = []
+ if opts.directory:
+ d = opts.directory
+ for f in os.listdir(d):
+ files.append(os.path.join(d, f))
+ files_to_process = []
+ for f in files:
+ m = file_matcher.match(f)
+ if m:
+ files_to_process.append((m.group(1), f))
+
+ url_matcher = re.compile(r".*screen-([\w-]+)\.txt\.gz")
+ urls = []
+ if opts.url:
+ for logfile in collect_url_logs(opts.url):
+ urls.append("%s/%s" % (opts.url, logfile))
+ urls_to_process = []
+ for u in urls:
+ m = url_matcher.match(u)
+ if m:
+ urls_to_process.append((m.group(1), u))
+
+ whitelists = {}
+ with open(WHITELIST_FILE) as stream:
+ loaded = yaml.safe_load(stream)
+ if loaded:
+ for (name, l) in loaded.iteritems():
+ for w in l:
+ assert 'module' in w, 'no module in %s' % name
+ assert 'message' in w, 'no message in %s' % name
+ whitelists = loaded
+ if process_files(files_to_process, urls_to_process, whitelists):
+ print("Logs have errors")
+ if is_neutron:
+ print("Currently not failing neutron builds with errors")
+ return 0
+ # Return non-zero to start failing builds
+ return 0
+ else:
+ print("ok")
+ return 0
+
+usage = """
+Find non-white-listed log errors in log files from a devstack-gate run.
+Log files will be searched for ERROR or CRITICAL messages. If any
+error messages do not match any of the whitelist entries contained in
+etc/whitelist.yaml, those messages will be printed to the console and
+failure will be returned. A file directory containing logs or a url to the
+log files of an OpenStack gate job can be provided.
+
+The whitelist yaml looks like:
+
+log-name:
+ - module: "a.b.c"
+ message: "regexp"
+ - module: "a.b.c"
+ message: "regexp"
+
+repeated for each log file with a whitelist.
+"""
+
+parser = argparse.ArgumentParser(description=usage)
+parser.add_argument('-d', '--directory',
+ help="Directory containing log files")
+parser.add_argument('-u', '--url',
+ help="url containing logs from an OpenStack gate job")
if __name__ == "__main__":
- sys.exit(0)
+ try:
+ sys.exit(main(parser.parse_args()))
+ except Exception as e:
+ print("Failure in script: %s" % e)
+ # Don't fail if there is a problem with the script.
+ sys.exit(0)
diff --git a/tools/config/check_uptodate.sh b/tools/config/check_uptodate.sh
new file mode 100755
index 0000000..45c8629
--- /dev/null
+++ b/tools/config/check_uptodate.sh
@@ -0,0 +1,10 @@
+#!/bin/sh
+TEMPDIR=`mktemp -d`
+CFGFILE=tempest.conf.sample
+tools/config/generate_sample.sh -b ./ -p tempest -o $TEMPDIR
+if ! diff $TEMPDIR/$CFGFILE etc/$CFGFILE
+then
+ echo "E: tempest.conf.sample is not up to date, please run:"
+ echo "tools/generate_sample.sh"
+ exit 42
+fi
diff --git a/tools/config/generate_sample.sh b/tools/config/generate_sample.sh
new file mode 100755
index 0000000..b86e0c2
--- /dev/null
+++ b/tools/config/generate_sample.sh
@@ -0,0 +1,93 @@
+#!/usr/bin/env bash
+
+print_hint() {
+ echo "Try \`${0##*/} --help' for more information." >&2
+}
+
+PARSED_OPTIONS=$(getopt -n "${0##*/}" -o hb:p:o: \
+ --long help,base-dir:,package-name:,output-dir: -- "$@")
+
+if [ $? != 0 ] ; then print_hint ; exit 1 ; fi
+
+eval set -- "$PARSED_OPTIONS"
+
+while true; do
+ case "$1" in
+ -h|--help)
+ echo "${0##*/} [options]"
+ echo ""
+ echo "options:"
+ echo "-h, --help show brief help"
+ echo "-b, --base-dir=DIR project base directory"
+ echo "-p, --package-name=NAME project package name"
+ echo "-o, --output-dir=DIR file output directory"
+ exit 0
+ ;;
+ -b|--base-dir)
+ shift
+ BASEDIR=`echo $1 | sed -e 's/\/*$//g'`
+ shift
+ ;;
+ -p|--package-name)
+ shift
+ PACKAGENAME=`echo $1`
+ shift
+ ;;
+ -o|--output-dir)
+ shift
+ OUTPUTDIR=`echo $1 | sed -e 's/\/*$//g'`
+ shift
+ ;;
+ --)
+ break
+ ;;
+ esac
+done
+
+BASEDIR=${BASEDIR:-`pwd`}
+if ! [ -d $BASEDIR ]
+then
+ echo "${0##*/}: missing project base directory" >&2 ; print_hint ; exit 1
+elif [[ $BASEDIR != /* ]]
+then
+ BASEDIR=$(cd "$BASEDIR" && pwd)
+fi
+
+PACKAGENAME=${PACKAGENAME:-${BASEDIR##*/}}
+TARGETDIR=$BASEDIR/$PACKAGENAME
+if ! [ -d $TARGETDIR ]
+then
+ echo "${0##*/}: invalid project package name" >&2 ; print_hint ; exit 1
+fi
+
+OUTPUTDIR=${OUTPUTDIR:-$BASEDIR/etc}
+# NOTE(bnemec): Some projects put their sample config in etc/,
+# some in etc/$PACKAGENAME/
+if [ -d $OUTPUTDIR/$PACKAGENAME ]
+then
+ OUTPUTDIR=$OUTPUTDIR/$PACKAGENAME
+elif ! [ -d $OUTPUTDIR ]
+then
+ echo "${0##*/}: cannot access \`$OUTPUTDIR': No such file or directory" >&2
+ exit 1
+fi
+
+BASEDIRESC=`echo $BASEDIR | sed -e 's/\//\\\\\//g'`
+find $TARGETDIR -type f -name "*.pyc" -delete
+FILES=$(find $TARGETDIR -type f -name "*.py" ! -path "*/tests/*" \
+ -exec grep -l "Opt(" {} + | sed -e "s/^$BASEDIRESC\///g" | sort -u)
+
+EXTRA_MODULES_FILE="`dirname $0`/oslo.config.generator.rc"
+if test -r "$EXTRA_MODULES_FILE"
+then
+ source "$EXTRA_MODULES_FILE"
+fi
+
+export EVENTLET_NO_GREENDNS=yes
+
+OS_VARS=$(set | sed -n '/^OS_/s/=[^=]*$//gp' | xargs)
+[ "$OS_VARS" ] && eval "unset \$OS_VARS"
+DEFAULT_MODULEPATH=tempest.openstack.common.config.generator
+MODULEPATH=${MODULEPATH:-$DEFAULT_MODULEPATH}
+OUTPUTFILE=$OUTPUTDIR/$PACKAGENAME.conf.sample
+python -m $MODULEPATH $FILES > $OUTPUTFILE
diff --git a/tools/find_stack_traces.py b/tools/find_stack_traces.py
index 0ce1500..52a5a66 100755
--- a/tools/find_stack_traces.py
+++ b/tools/find_stack_traces.py
@@ -65,7 +65,9 @@
def hunt_for_stacktrace(url):
"""Return TRACE or ERROR lines out of logs."""
- page = urllib2.urlopen(url)
+ req = urllib2.Request(url)
+ req.add_header('Accept-Encoding', 'gzip')
+ page = urllib2.urlopen(req)
buf = StringIO.StringIO(page.read())
f = gzip.GzipFile(fileobj=buf)
content = f.read()
diff --git a/tools/generate_sample.sh b/tools/generate_sample.sh
new file mode 100755
index 0000000..9b312c9
--- /dev/null
+++ b/tools/generate_sample.sh
@@ -0,0 +1,2 @@
+#!/bin/sh
+MODULEPATH=tempest.common.generate_sample_tempest tools/config/generate_sample.sh $@
diff --git a/tools/install_venv_common.py b/tools/install_venv_common.py
index 92d66ae..1bab88a 100644
--- a/tools/install_venv_common.py
+++ b/tools/install_venv_common.py
@@ -121,9 +121,6 @@
self.pip_install('-r', self.requirements, '-r', self.test_requirements)
- def post_process(self):
- self.get_distro().post_process()
-
def parse_args(self, argv):
"""Parses command-line arguments."""
parser = optparse.OptionParser()
@@ -156,14 +153,6 @@
' requires virtualenv, please install it using your'
' favorite package management tool' % self.project)
- def post_process(self):
- """Any distribution-specific post-processing gets done here.
-
- In particular, this is useful for applying patches to code inside
- the venv.
- """
- pass
-
class Fedora(Distro):
"""This covers all Fedora-based distributions.
@@ -175,10 +164,6 @@
return self.run_command_with_code(['rpm', '-q', pkg],
check_exit_code=False)[1] == 0
- def apply_patch(self, originalfile, patchfile):
- self.run_command(['patch', '-N', originalfile, patchfile],
- check_exit_code=False)
-
def install_virtualenv(self):
if self.check_cmd('virtualenv'):
return
@@ -187,27 +172,3 @@
self.die("Please install 'python-virtualenv'.")
super(Fedora, self).install_virtualenv()
-
- def post_process(self):
- """Workaround for a bug in eventlet.
-
- This currently affects RHEL6.1, but the fix can safely be
- applied to all RHEL and Fedora distributions.
-
- This can be removed when the fix is applied upstream.
-
- Nova: https://bugs.launchpad.net/nova/+bug/884915
- Upstream: https://bitbucket.org/eventlet/eventlet/issue/89
- RHEL: https://bugzilla.redhat.com/958868
- """
-
- if os.path.exists('contrib/redhat-eventlet.patch'):
- # Install "patch" program if it's not there
- if not self.check_pkg('patch'):
- self.die("Please install 'patch'.")
-
- # Apply the eventlet patch
- self.apply_patch(os.path.join(self.venv, 'lib', self.py_version,
- 'site-packages',
- 'eventlet/green/subprocess.py'),
- 'contrib/redhat-eventlet.patch')
diff --git a/tools/skip_tracker.py b/tools/skip_tracker.py
index ffaf134..0ae3323 100755
--- a/tools/skip_tracker.py
+++ b/tools/skip_tracker.py
@@ -46,14 +46,24 @@
test methods that have been decorated to skip because of
a particular bug.
"""
- results = []
+ results = {}
debug("Searching in %s", start)
for root, _dirs, files in os.walk(start):
for name in files:
if name.startswith('test_') and name.endswith('py'):
path = os.path.join(root, name)
debug("Searching in %s", path)
- results += find_skips_in_file(path)
+ temp_result = find_skips_in_file(path)
+ for method_name, bug_no in temp_result:
+ if results.get(bug_no):
+ result_dict = results.get(bug_no)
+ if result_dict.get(name):
+ result_dict[name].append(method_name)
+ else:
+ result_dict[name] = [method_name]
+ results[bug_no] = result_dict
+ else:
+ results[bug_no] = {name: [method_name]}
return results
@@ -83,11 +93,19 @@
return results
+def get_results(result_dict):
+ results = []
+ for bug_no in result_dict.keys():
+ for method in result_dict[bug_no]:
+ results.append((method, bug_no))
+ return results
+
+
if __name__ == '__main__':
logging.basicConfig(format='%(levelname)s: %(message)s',
level=logging.INFO)
results = find_skips()
- unique_bugs = sorted(set([bug for (method, bug) in results]))
+ unique_bugs = sorted(set([bug for (method, bug) in get_results(results)]))
unskips = []
duplicates = []
info("Total bug skips found: %d", len(results))
@@ -122,4 +140,7 @@
print("should be removed from the test cases:")
print()
for bug in unskips:
- print(" %7s" % bug)
+ message = " %7s in " % bug
+ locations = ["%s" % x for x in results[bug].keys()]
+ message += " and ".join(locations)
+ print(message)
diff --git a/tools/tempest_auto_config.py b/tools/tempest_auto_config.py
index aef6a1f..fe9f5af 100644
--- a/tools/tempest_auto_config.py
+++ b/tools/tempest_auto_config.py
@@ -14,38 +14,50 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
+#
+# This script aims to configure an initial Openstack environment with all the
+# necessary configurations for tempest's run using nothing but Openstack's
+# native API.
+# That includes, creating users, tenants, registering images (cirros),
+# configuring neutron and so on.
+#
+# ASSUMPTION: this script is run by an admin user as it is meant to configure
+# the Openstack environment prior to actual use.
# Config
import ConfigParser
import os
+import tarfile
+import urllib2
# Default client libs
+import glanceclient as glance_client
import keystoneclient.v2_0.client as keystone_client
# Import Openstack exceptions
+import glanceclient.exc as glance_exception
import keystoneclient.exceptions as keystone_exception
-DEFAULT_CONFIG_DIR = "%s/etc" % os.path.abspath(os.path.pardir)
-DEFAULT_CONFIG_FILE = "tempest.conf"
-DEFAULT_CONFIG_SAMPLE = "tempest.conf.sample"
+TEMPEST_TEMP_DIR = os.getenv("TEMPEST_TEMP_DIR", "/tmp").rstrip('/')
+TEMPEST_ROOT_DIR = os.getenv("TEMPEST_ROOT_DIR", os.getenv("HOME")).rstrip('/')
# Environment variables override defaults
-TEMPEST_CONFIG_DIR = os.environ.get('TEMPEST_CONFIG_DIR') or DEFAULT_CONFIG_DIR
-TEMPEST_CONFIG = os.environ.get('TEMPEST_CONFIG') or "%s/%s" % \
- (TEMPEST_CONFIG_DIR, DEFAULT_CONFIG_FILE)
-TEMPEST_CONFIG_SAMPLE = os.environ.get('TEMPEST_CONFIG_SAMPLE') or "%s/%s" % \
- (TEMPEST_CONFIG_DIR, DEFAULT_CONFIG_SAMPLE)
-
-# Admin credentials
-OS_USERNAME = os.environ.get('OS_USERNAME')
-OS_PASSWORD = os.environ.get('OS_PASSWORD')
-OS_TENANT_NAME = os.environ.get('OS_TENANT_NAME')
-OS_AUTH_URL = os.environ.get('OS_AUTH_URL')
-
+TEMPEST_CONFIG_DIR = os.getenv("TEMPEST_CONFIG_DIR",
+ "%s%s" % (TEMPEST_ROOT_DIR, "/etc")).rstrip('/')
+TEMPEST_CONFIG_FILE = os.getenv("TEMPEST_CONFIG_FILE",
+ "%s%s" % (TEMPEST_CONFIG_DIR, "/tempest.conf"))
+TEMPEST_CONFIG_SAMPLE = os.getenv("TEMPEST_CONFIG_SAMPLE",
+ "%s%s" % (TEMPEST_CONFIG_DIR,
+ "/tempest.conf.sample"))
# Image references
-IMAGE_ID = os.environ.get('IMAGE_ID')
-IMAGE_ID_ALT = os.environ.get('IMAGE_ID_ALT')
+IMAGE_DOWNLOAD_CHUNK_SIZE = 8 * 1024
+IMAGE_UEC_SOURCE_URL = os.getenv("IMAGE_UEC_SOURCE_URL",
+ "http://download.cirros-cloud.net/0.3.1/"
+ "cirros-0.3.1-x86_64-uec.tar.gz")
+TEMPEST_IMAGE_ID = os.getenv('IMAGE_ID')
+TEMPEST_IMAGE_ID_ALT = os.getenv('IMAGE_ID_ALT')
+IMAGE_STATUS_ACTIVE = 'active'
class ClientManager(object):
@@ -76,26 +88,52 @@
return self.identity_client
+ def get_image_client(self, version="1", *args, **kwargs):
+ """
+ This method returns Openstack glance python client
+ :param version: a string representing the version of the glance client
+ to use.
+ :param string endpoint: A user-supplied endpoint URL for the glance
+ service.
+ :param string token: Token for authentication.
+ :param integer timeout: Allows customization of the timeout for client
+ http requests. (optional)
+ :return: a Client object representing the glance client
+ """
+ if not self.image_client:
+ self.image_client = glance_client.Client(version, *args, **kwargs)
-def getTempestConfigSample():
+ return self.image_client
+
+
+def get_tempest_config(path_to_config):
"""
Gets the tempest configuration file as a ConfigParser object
- :return: the tempest configuration file
+ :param path_to_config: path to the config file
+ :return: a ConfigParser object representing the tempest configuration file
"""
# get the sample config file from the sample
- config_sample = ConfigParser.ConfigParser()
- config_sample.readfp(open(TEMPEST_CONFIG_SAMPLE))
+ config = ConfigParser.ConfigParser()
+ config.readfp(open(path_to_config))
- return config_sample
+ return config
def update_config_admin_credentials(config, config_section):
"""
Updates the tempest config with the admin credentials
- :param config: an object representing the tempest config file
+ :param config: a ConfigParser object representing the tempest config file
:param config_section: the section name where the admin credentials are
"""
- # Check if credentials are present
+ # Check if credentials are present, default uses the config credentials
+ OS_USERNAME = os.getenv('OS_USERNAME',
+ config.get(config_section, "admin_username"))
+ OS_PASSWORD = os.getenv('OS_PASSWORD',
+ config.get(config_section, "admin_password"))
+ OS_TENANT_NAME = os.getenv('OS_TENANT_NAME',
+ config.get(config_section, "admin_tenant_name"))
+ OS_AUTH_URL = os.getenv('OS_AUTH_URL', config.get(config_section, "uri"))
+
if not (OS_AUTH_URL and
OS_USERNAME and
OS_PASSWORD and
@@ -113,31 +151,31 @@
config_identity_params)
-def update_config_section_with_params(config, section, params):
+def update_config_section_with_params(config, config_section, params):
"""
Updates a given config object with given params
- :param config: the object representing the config file of tempest
- :param section: the section we would like to update
+ :param config: a ConfigParser object representing the tempest config file
+ :param config_section: the section we would like to update
:param params: the parameters we wish to update for that section
"""
for option, value in params.items():
- config.set(section, option, value)
+ config.set(config_section, option, value)
-def get_identity_client_kwargs(config, section_name):
+def get_identity_client_kwargs(config, config_section):
"""
Get the required arguments for the identity python client
- :param config: the tempest configuration file
- :param section_name: the section name in the configuration where the
+ :param config: a ConfigParser object representing the tempest config file
+ :param config_section: the section name in the configuration where the
arguments can be found
:return: a dictionary representing the needed arguments for the identity
client
"""
- username = config.get(section_name, 'admin_username')
- password = config.get(section_name, 'admin_password')
- tenant_name = config.get(section_name, 'admin_tenant_name')
- auth_url = config.get(section_name, 'uri')
- dscv = config.get(section_name, 'disable_ssl_certificate_validation')
+ username = config.get(config_section, 'admin_username')
+ password = config.get(config_section, 'admin_password')
+ tenant_name = config.get(config_section, 'admin_tenant_name')
+ auth_url = config.get(config_section, 'uri')
+ dscv = config.get(config_section, 'disable_ssl_certificate_validation')
kwargs = {'username': username,
'password': password,
'tenant_name': tenant_name,
@@ -185,21 +223,21 @@
def create_users_and_tenants(identity_client,
config,
- identity_section):
+ config_section):
"""
Creates the two non admin users and tenants for tempest
:param identity_client: openstack identity python client
- :param config: tempest configuration file
- :param identity_section: the section name of identity in the config
+ :param config: a ConfigParser object representing the tempest config file
+ :param config_section: the section name of identity in the config
"""
# Get the necessary params from the config file
- tenant_name = config.get(identity_section, 'tenant_name')
- username = config.get(identity_section, 'username')
- password = config.get(identity_section, 'password')
+ tenant_name = config.get(config_section, 'tenant_name')
+ username = config.get(config_section, 'username')
+ password = config.get(config_section, 'password')
- alt_tenant_name = config.get(identity_section, 'alt_tenant_name')
- alt_username = config.get(identity_section, 'alt_username')
- alt_password = config.get(identity_section, 'alt_password')
+ alt_tenant_name = config.get(config_section, 'alt_tenant_name')
+ alt_username = config.get(config_section, 'alt_username')
+ alt_password = config.get(config_section, 'alt_password')
# Create the necessary users for the test runs
create_user_with_tenant(identity_client, username, password, tenant_name)
@@ -207,28 +245,153 @@
alt_tenant_name)
+def get_image_client_kwargs(identity_client, config, config_section):
+ """
+ Get the required arguments for the image python client
+ :param identity_client: openstack identity python client
+ :param config: a ConfigParser object representing the tempest config file
+ :param config_section: the section name of identity in the config
+ :return: a dictionary representing the needed arguments for the image
+ client
+ """
+
+ token = identity_client.auth_token
+ endpoint = identity_client.\
+ service_catalog.url_for(service_type='image', endpoint_type='publicURL'
+ )
+ dscv = config.get(config_section, 'disable_ssl_certificate_validation')
+ kwargs = {'endpoint': endpoint,
+ 'token': token,
+ 'insecure': dscv}
+
+ return kwargs
+
+
+def images_exist(image_client):
+ """
+ Checks whether the images ID's located in the environment variable are
+ indeed registered
+ :param image_client: the openstack python client representing the image
+ client
+ """
+ exist = True
+ if not TEMPEST_IMAGE_ID or not TEMPEST_IMAGE_ID_ALT:
+ exist = False
+ else:
+ try:
+ image_client.images.get(TEMPEST_IMAGE_ID)
+ image_client.images.get(TEMPEST_IMAGE_ID_ALT)
+ except glance_exception.HTTPNotFound:
+ exist = False
+
+ return exist
+
+
+def download_and_register_uec_images(image_client, download_url,
+ download_folder):
+ """
+ Downloads and registered the UEC AKI/AMI/ARI images
+ :param image_client:
+ :param download_url: the url of the uec tar file
+ :param download_folder: the destination folder we wish to save the file to
+ """
+ basename = os.path.basename(download_url)
+ path = os.path.join(download_folder, basename)
+
+ request = urllib2.urlopen(download_url)
+
+ # First, download the file
+ with open(path, "wb") as fp:
+ while True:
+ chunk = request.read(IMAGE_DOWNLOAD_CHUNK_SIZE)
+ if not chunk:
+ break
+
+ fp.write(chunk)
+
+ # Then extract and register images
+ tar = tarfile.open(path, "r")
+ for name in tar.getnames():
+ file_obj = tar.extractfile(name)
+ format = "aki"
+
+ if file_obj.name.endswith(".img"):
+ format = "ami"
+
+ if file_obj.name.endswith("initrd"):
+ format = "ari"
+
+ # Register images in image client
+ image_client.images.create(name=file_obj.name, disk_format=format,
+ container_format=format, data=file_obj,
+ is_public="true")
+
+ tar.close()
+
+
+def create_images(image_client, config, config_section,
+ download_url=IMAGE_UEC_SOURCE_URL,
+ download_folder=TEMPEST_TEMP_DIR):
+ """
+ Creates images for tempest's use and registers the environment variables
+ IMAGE_ID and IMAGE_ID_ALT with registered images
+ :param image_client: Openstack python image client
+ :param config: a ConfigParser object representing the tempest config file
+ :param config_section: the section name where the IMAGE ids are set
+ :param download_url: the URL from which we should download the UEC tar
+ :param download_folder: the place where we want to save the download file
+ """
+ if not images_exist(image_client):
+ # Falls down to the default uec images
+ download_and_register_uec_images(image_client, download_url,
+ download_folder)
+ image_ids = []
+ for image in image_client.images.list():
+ image_ids.append(image.id)
+
+ os.environ["IMAGE_ID"] = image_ids[0]
+ os.environ["IMAGE_ID_ALT"] = image_ids[1]
+
+ params = {'image_ref': os.getenv("IMAGE_ID"),
+ 'image_ref_alt': os.getenv("IMAGE_ID_ALT")}
+
+ update_config_section_with_params(config, config_section, params)
+
+
def main():
"""
Main module to control the script
"""
- # TODO(tkammer): add support for existing config file
- config_sample = getTempestConfigSample()
- update_config_admin_credentials(config_sample, 'identity')
+ # Check if config file exists or fall to the default sample otherwise
+ path_to_config = TEMPEST_CONFIG_SAMPLE
+
+ if os.path.isfile(TEMPEST_CONFIG_FILE):
+ path_to_config = TEMPEST_CONFIG_FILE
+
+ config = get_tempest_config(path_to_config)
+ update_config_admin_credentials(config, 'identity')
client_manager = ClientManager()
# Set the identity related info for tempest
- identity_client_kwargs = get_identity_client_kwargs(config_sample,
+ identity_client_kwargs = get_identity_client_kwargs(config,
'identity')
identity_client = client_manager.get_identity_client(
**identity_client_kwargs)
# Create the necessary users and tenants for tempest run
- create_users_and_tenants(identity_client,
- config_sample,
- 'identity')
+ create_users_and_tenants(identity_client, config, 'identity')
- # TODO(tkammer): add image implementation
+ # Set the image related info for tempest
+ image_client_kwargs = get_image_client_kwargs(identity_client,
+ config,
+ 'identity')
+ image_client = client_manager.get_image_client(**image_client_kwargs)
+
+ # Create the necessary users and tenants for tempest run
+ create_images(image_client, config, 'compute')
+
+ # TODO(tkammer): add network implementation
if __name__ == "__main__":
main()
diff --git a/tools/verify_tempest_config.py b/tools/verify_tempest_config.py
new file mode 100755
index 0000000..1b5fe68
--- /dev/null
+++ b/tools/verify_tempest_config.py
@@ -0,0 +1,77 @@
+#!/usr/bin/env python
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 IBM Corp.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import sys
+
+from tempest import clients
+from tempest import config
+
+
+CONF = config.TempestConfig()
+
+#Dicts matching extension names to config options
+NOVA_EXTENSIONS = {
+ 'disk_config': 'DiskConfig',
+ 'change_password': 'ServerPassword',
+ 'flavor_extra': 'FlavorExtraSpecs'
+}
+
+
+def verify_glance_api_versions(os):
+ # Check glance api versions
+ __, versions = os.image_client.get_versions()
+ if CONF.image_feature_enabled.api_v1 != ('v1.1' in versions or 'v1.0' in
+ versions):
+ print 'Config option image api_v1 should be change to: %s' % (
+ not CONF.image_feature_enabled.api_v1)
+ if CONF.image_feature_enabled.api_v2 != ('v2.0' in versions):
+ print 'Config option image api_v2 should be change to: %s' % (
+ not CONF.image_feature_enabled.api_v2)
+
+
+def verify_extensions(os):
+ results = {}
+ extensions_client = os.extensions_client
+ __, resp = extensions_client.list_extensions()
+ resp = resp['extensions']
+ extensions = map(lambda x: x['name'], resp)
+ results['nova_features'] = {}
+ for extension in NOVA_EXTENSIONS.keys():
+ if NOVA_EXTENSIONS[extension] in extensions:
+ results['nova_features'][extension] = True
+ else:
+ results['nova_features'][extension] = False
+ return results
+
+
+def display_results(results):
+ for option in NOVA_EXTENSIONS.keys():
+ config_value = getattr(CONF.compute_feature_enabled, option)
+ if config_value != results['nova_features'][option]:
+ print "Config option: %s should be changed to: %s" % (
+ option, not config_value)
+
+
+def main(argv):
+ os = clients.ComputeAdminManager(interface='json')
+ results = verify_extensions(os)
+ verify_glance_api_versions(os)
+ display_results(results)
+
+
+if __name__ == "__main__":
+ main(sys.argv)
diff --git a/tox.ini b/tox.ini
index ff09b3f..6efac78 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,11 +1,23 @@
[tox]
envlist = pep8
+minversion = 1.6
+skipsdist = True
[testenv]
setenv = VIRTUAL_ENV={envdir}
LANG=en_US.UTF-8
LANGUAGE=en_US:en
LC_ALL=C
+usedevelop = True
+
+[testenv:py26]
+commands = python setup.py test --slowest --testr-arg='tempest\.tests {posargs}'
+
+[testenv:py33]
+commands = python setup.py test --slowest --testr-arg='tempest\.tests {posargs}'
+
+[testenv:py27]
+commands = python setup.py test --slowest --testr-arg='tempest\.tests {posargs}'
[testenv:all]
sitepackages = True
@@ -15,29 +27,25 @@
[testenv:full]
sitepackages = True
-setenv = VIRTUAL_ENV={envdir}
# The regex below is used to select which tests to run and exclude the slow tag:
# See the testrepostiory bug: https://bugs.launchpad.net/testrepository/+bug/1208610
commands =
- sh tools/pretty_tox.sh '(?!.*\[.*\bslow\b.*\])(^tempest\.(api|scenario|thirdparty|cli|tests)) {posargs}'
+ sh tools/pretty_tox.sh '(?!.*\[.*\bslow\b.*\])(^tempest\.(api|scenario|thirdparty|cli)) {posargs}'
[testenv:testr-full]
sitepackages = True
-setenv = VIRTUAL_ENV={envdir}
commands =
- sh tools/pretty_tox.sh '(?!.*\[.*\bslow\b.*\])(^tempest\.(api|scenario|thirdparty|cli|tests)) {posargs}'
+ sh tools/pretty_tox.sh '(?!.*\[.*\bslow\b.*\])(^tempest\.(api|scenario|thirdparty|cli)) {posargs}'
[testenv:heat-slow]
sitepackages = True
-setenv = VIRTUAL_ENV={envdir}
- OS_TEST_TIMEOUT=1200
+setenv = OS_TEST_TIMEOUT=1200
# The regex below is used to select heat api/scenario tests tagged as slow.
commands =
sh tools/pretty_tox_serial.sh '(?=.*\[.*\bslow\b.*\])(^tempest\.(api|scenario)\.orchestration) {posargs}'
[testenv:large-ops]
sitepackages = True
-setenv = VIRTUAL_ENV={envdir}
commands =
python setup.py testr --slowest --testr-args='tempest.scenario.test_large_ops {posargs}'
@@ -53,7 +61,7 @@
NOSE_OPENSTACK_STDOUT=1
TEMPEST_PY26_NOSE_COMPAT=1
commands =
- nosetests --logging-format '%(asctime)-15s %(message)s' --with-xunit -sv --xunit-file=nosetests-full.xml tempest/api tempest/scenario tempest/thirdparty tempest/cli tempest/tests {posargs}
+ nosetests --logging-format '%(asctime)-15s %(message)s' --with-xunit -sv --xunit-file=nosetests-full.xml tempest/api tempest/scenario tempest/thirdparty tempest/cli {posargs}
[testenv:py26-smoke]
setenv = VIRTUAL_ENV={envdir}
@@ -69,24 +77,26 @@
[testenv:smoke]
sitepackages = True
-setenv = VIRTUAL_ENV={envdir}
+commands =
+ sh tools/pretty_tox.sh '(?!.*\[.*\bslow\b.*\])((smoke)|(^tempest\.scenario)) {posargs}'
+
+[testenv:smoke-serial]
+sitepackages = True
# This is still serial because neutron doesn't work with parallel. See:
# https://bugs.launchpad.net/tempest/+bug/1216076 so the neutron smoke
# job would fail if we moved it to parallel.
commands =
- sh tools/pretty_tox_serial.sh 'smoke {posargs}'
+ sh tools/pretty_tox_serial.sh '(?!.*\[.*\bslow\b.*\])((smoke)|(^tempest\.scenario)) {posargs}'
[testenv:coverage]
sitepackages = True
-setenv = VIRTUAL_ENV={envdir}
commands =
python -m tools/tempest_coverage -c start --combine
- sh tools/pretty_tox.sh '(?!.*\[.*\bslow\b.*\])(^tempest\.(api|scenario|thirdparty|cli|tests))'
+ sh tools/pretty_tox.sh '(?!.*\[.*\bslow\b.*\])(^tempest\.(api|scenario|thirdparty|cli))'
python -m tools/tempest_coverage -c report --html {posargs}
[testenv:stress]
sitepackages = True
-setenv = VIRTUAL_ENV={envdir}
commands =
python -m tempest/stress/run_stress -a -d 3600 -S
@@ -96,7 +106,11 @@
-r{toxinidir}/test-requirements.txt
[testenv:pep8]
-commands = flake8
+setenv = MODULEPATH=tempest.common.generate_sample_tempest
+commands =
+ flake8 {posargs}
+ {toxinidir}/tools/config/check_uptodate.sh
+
deps = -r{toxinidir}/requirements.txt
-r{toxinidir}/test-requirements.txt