Merge "Support disabling server pausing for compute API"
diff --git a/.gitignore b/.gitignore
index 0f4880f..28a9b9c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -15,3 +15,5 @@
dist
build
.testrepository
+.coverage*
+cover/
diff --git a/etc/logging.conf.sample b/etc/logging.conf.sample
index 3b468f1..cdeedef 100644
--- a/etc/logging.conf.sample
+++ b/etc/logging.conf.sample
@@ -1,46 +1,40 @@
[loggers]
-keys=root,tempest,tempest_stress
+keys=root,tempest_stress
[handlers]
-keys=file,syslog,devel
+keys=file,devel,syslog
[formatters]
-keys=default,tests
+keys=simple,tests
[logger_root]
-level=NOTSET
-handlers=syslog
-
-[logger_tempest]
level=DEBUG
handlers=file
-qualname=tempest
[logger_tempest_stress]
-level=INFO
+level=DEBUG
handlers=file,devel
qualname=tempest.stress
[handler_file]
class=FileHandler
level=DEBUG
+args=('tempest.log', 'w+')
formatter=tests
-args=('tempest.log', 'w')
[handler_syslog]
class=handlers.SysLogHandler
level=ERROR
-formatter = default
args = ('/dev/log', handlers.SysLogHandler.LOG_USER)
[handler_devel]
class=StreamHandler
level=DEBUG
-formatter=default
args=(sys.stdout,)
-
-[formatter_default]
-format=%(name)s: %(levelname)s: %(message)s
+formatter=simple
[formatter_tests]
-class = tempest.common.log.TestsFormatter
+class = tempest.openstack.common.log.ContextFormatter
+
+[formatter_simple]
+format=%(asctime)s.%(msecs)03d %(process)d %(levelname)s: %(message)s
diff --git a/etc/schemas/compute/admin/flavor_create.json b/etc/schemas/compute/admin/flavor_create.json
new file mode 100644
index 0000000..0a3e7b3
--- /dev/null
+++ b/etc/schemas/compute/admin/flavor_create.json
@@ -0,0 +1,20 @@
+{
+ "name": "flavor-create",
+ "http-method": "POST",
+ "admin_client": true,
+ "url": "flavors",
+ "default_result_code": 400,
+ "json-schema": {
+ "type": "object",
+ "properties": {
+ "name": { "type": "string"},
+ "ram": { "type": "integer", "minimum": 1},
+ "vcpus": { "type": "integer", "minimum": 1},
+ "disk": { "type": "integer"},
+ "id": { "type": "integer"},
+ "swap": { "type": "integer"},
+ "rxtx_factor": { "type": "integer"},
+ "OS-FLV-EXT-DATA:ephemeral": { "type": "integer"}
+ }
+ }
+}
diff --git a/etc/tempest.conf.sample b/etc/tempest.conf.sample
index eb8d458..6655d5a 100644
--- a/etc/tempest.conf.sample
+++ b/etc/tempest.conf.sample
@@ -128,6 +128,9 @@
# AWS Access Key (string value)
#aws_access=<None>
+# AWS Zone for EC2 tests (string value)
+#aws_zone=nova
+
# S3 Materials Path (string value)
#s3_materials_path=/opt/stack/devstack/files/images/s3-materials/cirros-0.3.0
@@ -307,14 +310,14 @@
# Administrative Username to use for Nova API requests.
# (string value)
-#username=admin
+#username=<None>
# Administrative Tenant name to use for Nova API requests.
# (string value)
-#tenant_name=admin
+#tenant_name=<None>
# API key to use when authenticating as admin. (string value)
-#password=pass
+#password=<None>
[compute-feature-enabled]
@@ -341,9 +344,6 @@
# 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
@@ -454,16 +454,16 @@
#endpoint_type=publicURL
# Username to use for Nova API requests. (string value)
-#username=demo
+#username=<None>
# Tenant name to use for Nova API requests. (string value)
-#tenant_name=demo
+#tenant_name=<None>
# Role required to administrate keystone. (string value)
#admin_role=admin
# API key to use when authenticating. (string value)
-#password=pass
+#password=<None>
# Username of alternate user to use for Nova API requests.
# (string value)
@@ -479,14 +479,14 @@
# Administrative Username to use for Keystone API requests.
# (string value)
-#admin_username=admin
+#admin_username=<None>
# Administrative Tenant name to use for Keystone API requests.
# (string value)
-#admin_tenant_name=admin
+#admin_tenant_name=<None>
# API key to use when authenticating as admin. (string value)
-#admin_password=pass
+#admin_password=<None>
[identity-feature-enabled]
@@ -620,6 +620,14 @@
# (string value)
#public_router_id=
+# Timeout in seconds to wait for network operation to
+# complete. (integer value)
+#build_timeout=300
+
+# Time in seconds between network operation status checks.
+# (integer value)
+#build_interval=10
+
[network-feature-enabled]
@@ -725,6 +733,16 @@
#max_template_size=524288
+[queuing]
+
+#
+# Options defined in tempest.config
+#
+
+# Catalog type of the Queuing service. (string value)
+#catalog_type=queuing
+
+
[scenario]
#
@@ -792,9 +810,9 @@
# value)
#horizon=true
-# Whether or not Savanna is expected to be available (boolean
+# Whether or not Sahara is expected to be available (boolean
# value)
-#savanna=false
+#sahara=false
# Whether or not Ironic is expected to be available (boolean
# value)
@@ -804,6 +822,10 @@
# value)
#trove=false
+# Whether or not Marconi is expected to be available (boolean
+# value)
+#marconi=false
+
[stress]
diff --git a/etc/whitelist.yaml b/etc/whitelist.yaml
index 2d8b741..e69de29 100644
--- a/etc/whitelist.yaml
+++ b/etc/whitelist.yaml
@@ -1,233 +0,0 @@
-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.virt.libvirt.driver"
- message: "Nova requires libvirt version 0\\.9\\.11 or greater"
- - 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"
- - module: "nova.virt.driver"
- message: "Info cache for instance .* could not be found"
-
-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"
- - module: "oslo.messaging.notify._impl_messaging"
- message: ".*"
-
-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: "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"
- - module: "ceilometer.compute.pollsters.net"
- message: ".*"
- - module: "ceilometer.compute.pollsters.disk"
- message: ".*"
-
-ceilometer-acentral:
- - module: "ceilometer.central.manager"
- message: "403 Forbidden"
- - module: "ceilometer.central.manager"
- message: "get_samples\\(\\) got an unexpected keyword argument 'resources'"
-
-ceilometer-alarm-evaluator:
- - module: "ceilometer.alarm.service"
- message: "alarm evaluation cycle failed"
- - module: "ceilometer.alarm.evaluator.threshold"
- message: ".*"
-
-ceilometer-api:
- - module: "wsme.api"
- message: ".*"
-
-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"
- - module: "nova.openstack.common.threadgroup"
- message: "Service with host .* topic conductor exists."
-
-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__'"
- - module: "nova.openstack.common.rpc.common"
- message: "Instance .* could not be found"
-
-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: "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"
-
-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.dispatcher.database"
- message: "\\(DataError\\) integer out of range"
- - 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: ".*"
-
-s-proxy:
- - module: "proxy-server"
- message: "Timeout talking to memcached"
diff --git a/requirements.txt b/requirements.txt
index 0bddca3..434e12e 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -9,7 +9,7 @@
netaddr>=0.7.6
python-glanceclient>=0.9.0
python-keystoneclient>=0.6.0
-python-novaclient>=2.15.0
+python-novaclient>=2.17.0
python-neutronclient>=2.3.4,<3
python-cinderclient>=1.0.6
python-heatclient>=0.2.3
@@ -20,6 +20,6 @@
testrepository>=0.0.18
oslo.config>=1.2.0
six>=1.5.2
-iso8601>=0.1.8
+iso8601>=0.1.9
fixtures>=0.3.14
testscenarios>=0.4
diff --git a/tempest/api/compute/admin/test_aggregates.py b/tempest/api/compute/admin/test_aggregates.py
index 6797005..fb249e5 100644
--- a/tempest/api/compute/admin/test_aggregates.py
+++ b/tempest/api/compute/admin/test_aggregates.py
@@ -16,7 +16,7 @@
from tempest.api.compute import base
from tempest.common import tempest_fixtures as fixtures
from tempest.common.utils import data_utils
-from tempest.test import attr
+from tempest import test
class AggregatesAdminTestJSON(base.BaseV2ComputeAdminTest):
@@ -39,7 +39,7 @@
filter(lambda y: y['service'] == 'compute', hosts_all))
cls.host = hosts[0]
- @attr(type='gate')
+ @test.attr(type='gate')
def test_aggregate_create_delete(self):
# Create and delete an aggregate.
aggregate_name = data_utils.rand_name(self.aggregate_name_prefix)
@@ -52,7 +52,7 @@
self.assertEqual(200, resp.status)
self.client.wait_for_resource_deletion(aggregate['id'])
- @attr(type='gate')
+ @test.attr(type='gate')
def test_aggregate_create_delete_with_az(self):
# Create and delete an aggregate.
aggregate_name = data_utils.rand_name(self.aggregate_name_prefix)
@@ -67,7 +67,7 @@
self.assertEqual(200, resp.status)
self.client.wait_for_resource_deletion(aggregate['id'])
- @attr(type='gate')
+ @test.attr(type='gate')
def test_aggregate_create_verify_entry_in_list(self):
# Create an aggregate and ensure it is listed.
aggregate_name = data_utils.rand_name(self.aggregate_name_prefix)
@@ -80,7 +80,7 @@
map(lambda x: (x['id'], x['availability_zone']),
aggregates))
- @attr(type='gate')
+ @test.attr(type='gate')
def test_aggregate_create_update_metadata_get_details(self):
# Create an aggregate and ensure its details are returned.
aggregate_name = data_utils.rand_name(self.aggregate_name_prefix)
@@ -105,7 +105,7 @@
self.assertEqual(200, resp.status)
self.assertEqual(meta, body["metadata"])
- @attr(type='gate')
+ @test.attr(type='gate')
def test_aggregate_create_update_with_az(self):
# Update an aggregate and ensure properties are updated correctly
aggregate_name = data_utils.rand_name(self.aggregate_name_prefix)
@@ -137,7 +137,7 @@
(x['id'], x['name'], x['availability_zone']),
aggregates))
- @attr(type='gate')
+ @test.attr(type='gate')
def test_aggregate_add_remove_host(self):
# Add an host to the given aggregate and remove.
self.useFixture(fixtures.LockFixture('availability_zone'))
@@ -159,7 +159,7 @@
body['availability_zone'])
self.assertNotIn(self.host, body['hosts'])
- @attr(type='gate')
+ @test.attr(type='gate')
def test_aggregate_add_host_list(self):
# Add an host to the given aggregate and list.
self.useFixture(fixtures.LockFixture('availability_zone'))
@@ -177,7 +177,7 @@
self.assertIsNone(agg['availability_zone'])
self.assertIn(self.host, agg['hosts'])
- @attr(type='gate')
+ @test.attr(type='gate')
def test_aggregate_add_host_get_details(self):
# Add an host to the given aggregate and get details.
self.useFixture(fixtures.LockFixture('availability_zone'))
@@ -192,7 +192,7 @@
self.assertIsNone(body['availability_zone'])
self.assertIn(self.host, body['hosts'])
- @attr(type='gate')
+ @test.attr(type='gate')
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'))
diff --git a/tempest/api/compute/admin/test_aggregates_negative.py b/tempest/api/compute/admin/test_aggregates_negative.py
index 3d34d47..690f2ab 100644
--- a/tempest/api/compute/admin/test_aggregates_negative.py
+++ b/tempest/api/compute/admin/test_aggregates_negative.py
@@ -17,7 +17,7 @@
from tempest.common import tempest_fixtures as fixtures
from tempest.common.utils import data_utils
from tempest import exceptions
-from tempest.test import attr
+from tempest import test
class AggregatesAdminNegativeTestJSON(base.BaseV2ComputeAdminTest):
@@ -39,7 +39,7 @@
filter(lambda y: y['service'] == 'compute', hosts_all))
cls.host = hosts[0]
- @attr(type=['negative', 'gate'])
+ @test.attr(type=['negative', 'gate'])
def test_aggregate_create_as_user(self):
# Regular user is not allowed to create an aggregate.
aggregate_name = data_utils.rand_name(self.aggregate_name_prefix)
@@ -47,14 +47,14 @@
self.user_client.create_aggregate,
name=aggregate_name)
- @attr(type=['negative', 'gate'])
+ @test.attr(type=['negative', 'gate'])
def test_aggregate_create_aggregate_name_length_less_than_1(self):
# the length of aggregate name should >= 1 and <=255
self.assertRaises(exceptions.BadRequest,
self.client.create_aggregate,
name='')
- @attr(type=['negative', 'gate'])
+ @test.attr(type=['negative', 'gate'])
def test_aggregate_create_aggregate_name_length_exceeds_255(self):
# the length of aggregate name should >= 1 and <=255
aggregate_name = 'a' * 256
@@ -62,7 +62,7 @@
self.client.create_aggregate,
name=aggregate_name)
- @attr(type=['negative', 'gate'])
+ @test.attr(type=['negative', 'gate'])
def test_aggregate_create_with_existent_aggregate_name(self):
# creating an aggregate with existent aggregate name is forbidden
aggregate_name = data_utils.rand_name(self.aggregate_name_prefix)
@@ -74,7 +74,7 @@
self.client.create_aggregate,
name=aggregate_name)
- @attr(type=['negative', 'gate'])
+ @test.attr(type=['negative', 'gate'])
def test_aggregate_delete_as_user(self):
# Regular user is not allowed to delete an aggregate.
aggregate_name = data_utils.rand_name(self.aggregate_name_prefix)
@@ -86,13 +86,13 @@
self.user_client.delete_aggregate,
aggregate['id'])
- @attr(type=['negative', 'gate'])
+ @test.attr(type=['negative', 'gate'])
def test_aggregate_list_as_user(self):
# Regular user is not allowed to list aggregates.
self.assertRaises(exceptions.Unauthorized,
self.user_client.list_aggregates)
- @attr(type=['negative', 'gate'])
+ @test.attr(type=['negative', 'gate'])
def test_aggregate_get_details_as_user(self):
# Regular user is not allowed to get aggregate details.
aggregate_name = data_utils.rand_name(self.aggregate_name_prefix)
@@ -104,19 +104,19 @@
self.user_client.get_aggregate,
aggregate['id'])
- @attr(type=['negative', 'gate'])
+ @test.attr(type=['negative', 'gate'])
def test_aggregate_delete_with_invalid_id(self):
# Delete an aggregate with invalid id should raise exceptions.
self.assertRaises(exceptions.NotFound,
self.client.delete_aggregate, -1)
- @attr(type=['negative', 'gate'])
+ @test.attr(type=['negative', 'gate'])
def test_aggregate_get_details_with_invalid_id(self):
# Get aggregate details with invalid id should raise exceptions.
self.assertRaises(exceptions.NotFound,
self.client.get_aggregate, -1)
- @attr(type=['negative', 'gate'])
+ @test.attr(type=['negative', 'gate'])
def test_aggregate_add_non_exist_host(self):
# Adding a non-exist host to an aggregate should raise exceptions.
resp, hosts_all = self.os_adm.hosts_client.list_hosts()
@@ -133,7 +133,7 @@
self.assertRaises(exceptions.NotFound, self.client.add_host,
aggregate['id'], non_exist_host)
- @attr(type=['negative', 'gate'])
+ @test.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 = data_utils.rand_name(self.aggregate_name_prefix)
@@ -145,7 +145,7 @@
self.user_client.add_host,
aggregate['id'], self.host)
- @attr(type=['negative', 'gate'])
+ @test.attr(type=['negative', 'gate'])
def test_aggregate_add_existent_host(self):
self.useFixture(fixtures.LockFixture('availability_zone'))
aggregate_name = data_utils.rand_name(self.aggregate_name_prefix)
@@ -160,7 +160,7 @@
self.assertRaises(exceptions.Conflict, self.client.add_host,
aggregate['id'], self.host)
- @attr(type=['negative', 'gate'])
+ @test.attr(type=['negative', 'gate'])
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'))
@@ -176,7 +176,7 @@
self.user_client.remove_host,
aggregate['id'], self.host)
- @attr(type=['negative', 'gate'])
+ @test.attr(type=['negative', 'gate'])
def test_aggregate_remove_nonexistent_host(self):
non_exist_host = data_utils.rand_name('nonexist_host_')
aggregate_name = data_utils.rand_name(self.aggregate_name_prefix)
diff --git a/tempest/api/compute/admin/test_availability_zone.py b/tempest/api/compute/admin/test_availability_zone.py
index 1387261..3c06624 100644
--- a/tempest/api/compute/admin/test_availability_zone.py
+++ b/tempest/api/compute/admin/test_availability_zone.py
@@ -14,7 +14,7 @@
# under the License.
from tempest.api.compute import base
-from tempest.test import attr
+from tempest import test
class AZAdminTestJSON(base.BaseV2ComputeAdminTest):
@@ -28,14 +28,14 @@
super(AZAdminTestJSON, cls).setUpClass()
cls.client = cls.os_adm.availability_zone_client
- @attr(type='gate')
+ @test.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')
+ @test.attr(type='gate')
def test_get_availability_zone_list_detail(self):
# List of availability zones and available services
resp, availability_zone = \
diff --git a/tempest/api/compute/admin/test_availability_zone_negative.py b/tempest/api/compute/admin/test_availability_zone_negative.py
index 8cc8bce..ce97491 100644
--- a/tempest/api/compute/admin/test_availability_zone_negative.py
+++ b/tempest/api/compute/admin/test_availability_zone_negative.py
@@ -14,7 +14,7 @@
from tempest.api.compute import base
from tempest import exceptions
-from tempest.test import attr
+from tempest import test
class AZAdminNegativeTestJSON(base.BaseV2ComputeAdminTest):
@@ -28,7 +28,7 @@
super(AZAdminNegativeTestJSON, cls).setUpClass()
cls.non_adm_client = cls.availability_zone_client
- @attr(type=['negative', 'gate'])
+ @test.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
diff --git a/tempest/api/compute/admin/test_fixed_ips.py b/tempest/api/compute/admin/test_fixed_ips.py
index 00bb9c3..b0692b1 100644
--- a/tempest/api/compute/admin/test_fixed_ips.py
+++ b/tempest/api/compute/admin/test_fixed_ips.py
@@ -15,7 +15,7 @@
from tempest.api.compute import base
from tempest import config
-from tempest.test import attr
+from tempest import test
CONF = config.CONF
@@ -39,18 +39,18 @@
if cls.ip:
break
- @attr(type='gate')
+ @test.attr(type='gate')
def test_list_fixed_ip_details(self):
resp, fixed_ip = self.client.get_fixed_ip_details(self.ip)
self.assertEqual(fixed_ip['address'], self.ip)
- @attr(type='gate')
+ @test.attr(type='gate')
def test_set_reserve(self):
body = {"reserve": "None"}
resp, body = self.client.reserve_fixed_ip(self.ip, body)
self.assertEqual(resp.status, 202)
- @attr(type='gate')
+ @test.attr(type='gate')
def test_set_unreserve(self):
body = {"unreserve": "None"}
resp, body = self.client.reserve_fixed_ip(self.ip, body)
diff --git a/tempest/api/compute/admin/test_fixed_ips_negative.py b/tempest/api/compute/admin/test_fixed_ips_negative.py
index 0faedb2..3fb3829 100644
--- a/tempest/api/compute/admin/test_fixed_ips_negative.py
+++ b/tempest/api/compute/admin/test_fixed_ips_negative.py
@@ -15,7 +15,7 @@
from tempest.api.compute import base
from tempest import config
from tempest import exceptions
-from tempest.test import attr
+from tempest import test
CONF = config.CONF
@@ -40,26 +40,26 @@
if cls.ip:
break
- @attr(type=['negative', 'gate'])
+ @test.attr(type=['negative', 'gate'])
def test_list_fixed_ip_details_with_non_admin_user(self):
self.assertRaises(exceptions.Unauthorized,
self.non_admin_client.get_fixed_ip_details, self.ip)
- @attr(type=['negative', 'gate'])
+ @test.attr(type=['negative', 'gate'])
def test_set_reserve_with_non_admin_user(self):
body = {"reserve": "None"}
self.assertRaises(exceptions.Unauthorized,
self.non_admin_client.reserve_fixed_ip,
self.ip, body)
- @attr(type=['negative', 'gate'])
+ @test.attr(type=['negative', 'gate'])
def test_set_unreserve_with_non_admin_user(self):
body = {"unreserve": "None"}
self.assertRaises(exceptions.Unauthorized,
self.non_admin_client.reserve_fixed_ip,
self.ip, body)
- @attr(type=['negative', 'gate'])
+ @test.attr(type=['negative', 'gate'])
def test_set_reserve_with_invalid_ip(self):
# NOTE(maurosr): since this exercises the same code snippet, we do it
# only for reserve action
@@ -68,7 +68,7 @@
self.client.reserve_fixed_ip,
"my.invalid.ip", body)
- @attr(type=['negative', 'gate'])
+ @test.attr(type=['negative', 'gate'])
def test_fixed_ip_with_invalid_action(self):
body = {"invalid_action": "None"}
self.assertRaises(exceptions.BadRequest,
diff --git a/tempest/api/compute/admin/test_flavors_negative.py b/tempest/api/compute/admin/test_flavors_negative.py
index 49d49ef..b882ff4 100644
--- a/tempest/api/compute/admin/test_flavors_negative.py
+++ b/tempest/api/compute/admin/test_flavors_negative.py
@@ -13,6 +13,7 @@
# License for the specific language governing permissions and limitations
# under the License.
+import testscenarios
import uuid
from tempest.api.compute import base
@@ -20,6 +21,8 @@
from tempest import exceptions
from tempest import test
+load_tests = testscenarios.load_tests_apply_scenarios
+
class FlavorsAdminNegativeTestJSON(base.BaseV2ComputeAdminTest):
@@ -44,11 +47,6 @@
cls.swap = 1024
cls.rxtx = 2
- def flavor_clean_up(self, flavor_id):
- resp, body = self.client.delete_flavor(flavor_id)
- self.assertEqual(resp.status, 202)
- self.client.wait_for_resource_deletion(flavor_id)
-
@test.attr(type=['negative', 'gate'])
def test_get_flavor_details_for_deleted_flavor(self):
# Delete a flavor and ensure it is not listed
@@ -85,13 +83,6 @@
self.assertTrue(flag)
@test.attr(type=['negative', 'gate'])
- def test_invalid_is_public_string(self):
- # the 'is_public' parameter can be 'none/true/false' if it exists
- self.assertRaises(exceptions.BadRequest,
- self.client.list_flavors_with_detail,
- {'is_public': 'invalid'})
-
- @test.attr(type=['negative', 'gate'])
def test_create_flavor_as_user(self):
# only admin user can create a flavor
flavor_name = data_utils.rand_name(self.flavor_name_prefix)
@@ -110,231 +101,16 @@
self.user_client.delete_flavor,
self.flavor_ref_alt)
- @test.attr(type=['negative', 'gate'])
- def test_create_flavor_using_invalid_ram(self):
- # the 'ram' attribute must be positive integer
- flavor_name = data_utils.rand_name(self.flavor_name_prefix)
- new_flavor_id = str(uuid.uuid4())
- self.assertRaises(exceptions.BadRequest,
- self.client.create_flavor,
- flavor_name, -1, self.vcpus,
- self.disk, new_flavor_id)
+class FlavorCreateNegativeTestJSON(base.BaseV2ComputeAdminTest,
+ test.NegativeAutoTest):
+ _interface = 'json'
+ _service = 'compute'
+ _schema_file = 'compute/admin/flavor_create.json'
+
+ scenarios = test.NegativeAutoTest.generate_scenario(_schema_file)
@test.attr(type=['negative', 'gate'])
- def test_create_flavor_using_invalid_vcpus(self):
- # the 'vcpu' attribute must be positive integer
- flavor_name = data_utils.rand_name(self.flavor_name_prefix)
- new_flavor_id = str(uuid.uuid4())
-
- self.assertRaises(exceptions.BadRequest,
- self.client.create_flavor,
- flavor_name, self.ram, -1,
- self.disk, new_flavor_id)
-
- @test.attr(type=['negative', 'gate'])
- def test_create_flavor_with_name_length_less_than_1(self):
- # ensure name length >= 1
- new_flavor_id = str(uuid.uuid4())
-
- self.assertRaises(exceptions.BadRequest,
- self.client.create_flavor,
- '',
- self.ram, self.vcpus,
- self.disk,
- new_flavor_id,
- ephemeral=self.ephemeral,
- swap=self.swap,
- rxtx=self.rxtx,
- is_public='False')
-
- @test.attr(type=['negative', 'gate'])
- def test_create_flavor_with_name_length_exceeds_255(self):
- # ensure name do not exceed 255 characters
- new_flavor_name = 'a' * 256
- new_flavor_id = str(uuid.uuid4())
-
- self.assertRaises(exceptions.BadRequest,
- self.client.create_flavor,
- new_flavor_name,
- self.ram, self.vcpus,
- self.disk,
- new_flavor_id,
- ephemeral=self.ephemeral,
- swap=self.swap,
- rxtx=self.rxtx,
- is_public='False')
-
- @test.attr(type=['negative', 'gate'])
- def test_create_flavor_with_invalid_name(self):
- # the regex of flavor_name is '^[\w\.\- ]*$'
- invalid_flavor_name = data_utils.rand_name('invalid-!@#$%-')
- new_flavor_id = str(uuid.uuid4())
-
- self.assertRaises(exceptions.BadRequest,
- self.client.create_flavor,
- invalid_flavor_name,
- self.ram, self.vcpus,
- self.disk,
- new_flavor_id,
- ephemeral=self.ephemeral,
- swap=self.swap,
- rxtx=self.rxtx,
- is_public='False')
-
- @test.attr(type=['negative', 'gate'])
- def test_create_flavor_with_invalid_flavor_id(self):
- # the regex of flavor_id is '^[\w\.\- ]*$', and it cannot contain
- # leading and/or trailing whitespace
- new_flavor_name = data_utils.rand_name(self.flavor_name_prefix)
- invalid_flavor_id = '!@#$%'
-
- self.assertRaises(exceptions.BadRequest,
- self.client.create_flavor,
- new_flavor_name,
- self.ram, self.vcpus,
- self.disk,
- invalid_flavor_id,
- ephemeral=self.ephemeral,
- swap=self.swap,
- rxtx=self.rxtx,
- is_public='False')
-
- @test.attr(type=['negative', 'gate'])
- def test_create_flavor_with_id_length_exceeds_255(self):
- # the length of flavor_id should not exceed 255 characters
- new_flavor_name = data_utils.rand_name(self.flavor_name_prefix)
- invalid_flavor_id = 'a' * 256
-
- self.assertRaises(exceptions.BadRequest,
- self.client.create_flavor,
- new_flavor_name,
- self.ram, self.vcpus,
- self.disk,
- invalid_flavor_id,
- ephemeral=self.ephemeral,
- swap=self.swap,
- rxtx=self.rxtx,
- is_public='False')
-
- @test.attr(type=['negative', 'gate'])
- def test_create_flavor_with_invalid_root_gb(self):
- # root_gb attribute should be non-negative ( >= 0) integer
- new_flavor_name = data_utils.rand_name(self.flavor_name_prefix)
- new_flavor_id = str(uuid.uuid4())
-
- self.assertRaises(exceptions.BadRequest,
- self.client.create_flavor,
- new_flavor_name,
- self.ram, self.vcpus,
- -1,
- new_flavor_id,
- ephemeral=self.ephemeral,
- swap=self.swap,
- rxtx=self.rxtx,
- is_public='False')
-
- @test.attr(type=['negative', 'gate'])
- def test_create_flavor_with_invalid_ephemeral_gb(self):
- # ephemeral_gb attribute should be non-negative ( >= 0) integer
- new_flavor_name = data_utils.rand_name(self.flavor_name_prefix)
- new_flavor_id = str(uuid.uuid4())
-
- self.assertRaises(exceptions.BadRequest,
- self.client.create_flavor,
- new_flavor_name,
- self.ram, self.vcpus,
- self.disk,
- new_flavor_id,
- ephemeral=-1,
- swap=self.swap,
- rxtx=self.rxtx,
- is_public='False')
-
- @test.attr(type=['negative', 'gate'])
- def test_create_flavor_with_invalid_swap(self):
- # swap attribute should be non-negative ( >= 0) integer
- new_flavor_name = data_utils.rand_name(self.flavor_name_prefix)
- new_flavor_id = str(uuid.uuid4())
-
- self.assertRaises(exceptions.BadRequest,
- self.client.create_flavor,
- new_flavor_name,
- self.ram, self.vcpus,
- self.disk,
- new_flavor_id,
- ephemeral=self.ephemeral,
- swap=-1,
- rxtx=self.rxtx,
- is_public='False')
-
- @test.attr(type=['negative', 'gate'])
- def test_create_flavor_with_invalid_rxtx_factor(self):
- # rxtx_factor attribute should be a positive float
- new_flavor_name = data_utils.rand_name(self.flavor_name_prefix)
- new_flavor_id = str(uuid.uuid4())
-
- self.assertRaises(exceptions.BadRequest,
- self.client.create_flavor,
- new_flavor_name,
- self.ram, self.vcpus,
- self.disk,
- new_flavor_id,
- ephemeral=self.ephemeral,
- swap=self.swap,
- rxtx=-1.5,
- is_public='False')
-
- @test.attr(type=['negative', 'gate'])
- def test_create_flavor_with_invalid_is_public(self):
- # is_public attribute should be boolean
- new_flavor_name = data_utils.rand_name(self.flavor_name_prefix)
- new_flavor_id = str(uuid.uuid4())
-
- self.assertRaises(exceptions.BadRequest,
- self.client.create_flavor,
- new_flavor_name,
- self.ram, self.vcpus,
- self.disk,
- new_flavor_id,
- ephemeral=self.ephemeral,
- swap=self.swap,
- rxtx=self.rxtx,
- is_public='Invalid')
-
- @test.attr(type=['negative', 'gate'])
- def test_create_flavor_already_exists(self):
- flavor_name = data_utils.rand_name(self.flavor_name_prefix)
- new_flavor_id = str(uuid.uuid4())
-
- resp, flavor = self.client.create_flavor(flavor_name,
- self.ram, self.vcpus,
- self.disk,
- new_flavor_id,
- ephemeral=self.ephemeral,
- swap=self.swap,
- rxtx=self.rxtx)
- self.assertEqual(200, resp.status)
- self.addCleanup(self.flavor_clean_up, flavor['id'])
-
- self.assertRaises(exceptions.Conflict,
- self.client.create_flavor,
- flavor_name,
- self.ram, self.vcpus,
- self.disk,
- new_flavor_id,
- ephemeral=self.ephemeral,
- swap=self.swap,
- rxtx=self.rxtx)
-
- @test.attr(type=['negative', 'gate'])
- def test_delete_nonexistent_flavor(self):
- nonexistent_flavor_id = str(uuid.uuid4())
-
- self.assertRaises(exceptions.NotFound,
- self.client.delete_flavor,
- nonexistent_flavor_id)
-
-
-class FlavorsAdminNegativeTestXML(FlavorsAdminNegativeTestJSON):
- _interface = 'xml'
+ def test_create_flavor(self):
+ # flavor details are not returned for non-existent flavors
+ self.execute(self._schema_file)
diff --git a/tempest/api/compute/admin/test_flavors_negative_xml.py b/tempest/api/compute/admin/test_flavors_negative_xml.py
new file mode 100644
index 0000000..a06b0e6
--- /dev/null
+++ b/tempest/api/compute/admin/test_flavors_negative_xml.py
@@ -0,0 +1,268 @@
+# 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 uuid
+
+from tempest.api.compute.admin import test_flavors_negative
+from tempest.common.utils import data_utils
+from tempest import exceptions
+from tempest import test
+
+
+class FlavorsAdminNegativeTestXML(test_flavors_negative.
+ FlavorsAdminNegativeTestJSON):
+
+ """
+ Tests Flavors API Create and Delete that require admin privileges
+ """
+
+ _interface = 'xml'
+
+ def flavor_clean_up(self, flavor_id):
+ resp, body = self.client.delete_flavor(flavor_id)
+ self.assertEqual(resp.status, 202)
+ self.client.wait_for_resource_deletion(flavor_id)
+
+ @test.attr(type=['negative', 'gate'])
+ def test_invalid_is_public_string(self):
+ # the 'is_public' parameter can be 'none/true/false' if it exists
+ self.assertRaises(exceptions.BadRequest,
+ self.client.list_flavors_with_detail,
+ {'is_public': 'invalid'})
+
+ @test.attr(type=['negative', 'gate'])
+ def test_create_flavor_using_invalid_ram(self):
+ # the 'ram' attribute must be positive integer
+ flavor_name = data_utils.rand_name(self.flavor_name_prefix)
+ new_flavor_id = str(uuid.uuid4())
+
+ self.assertRaises(exceptions.BadRequest,
+ self.client.create_flavor,
+ flavor_name, -1, self.vcpus,
+ self.disk, new_flavor_id)
+
+ @test.attr(type=['negative', 'gate'])
+ def test_create_flavor_using_invalid_vcpus(self):
+ # the 'vcpu' attribute must be positive integer
+ flavor_name = data_utils.rand_name(self.flavor_name_prefix)
+ new_flavor_id = str(uuid.uuid4())
+
+ self.assertRaises(exceptions.BadRequest,
+ self.client.create_flavor,
+ flavor_name, self.ram, -1,
+ self.disk, new_flavor_id)
+
+ @test.attr(type=['negative', 'gate'])
+ def test_create_flavor_with_name_length_less_than_1(self):
+ # ensure name length >= 1
+ new_flavor_id = str(uuid.uuid4())
+
+ self.assertRaises(exceptions.BadRequest,
+ self.client.create_flavor,
+ '',
+ self.ram, self.vcpus,
+ self.disk,
+ new_flavor_id,
+ ephemeral=self.ephemeral,
+ swap=self.swap,
+ rxtx=self.rxtx,
+ is_public='False')
+
+ @test.attr(type=['negative', 'gate'])
+ def test_create_flavor_with_name_length_exceeds_255(self):
+ # ensure name do not exceed 255 characters
+ new_flavor_name = 'a' * 256
+ new_flavor_id = str(uuid.uuid4())
+
+ self.assertRaises(exceptions.BadRequest,
+ self.client.create_flavor,
+ new_flavor_name,
+ self.ram, self.vcpus,
+ self.disk,
+ new_flavor_id,
+ ephemeral=self.ephemeral,
+ swap=self.swap,
+ rxtx=self.rxtx,
+ is_public='False')
+
+ @test.attr(type=['negative', 'gate'])
+ def test_create_flavor_with_invalid_name(self):
+ # the regex of flavor_name is '^[\w\.\- ]*$'
+ invalid_flavor_name = data_utils.rand_name('invalid-!@#$%-')
+ new_flavor_id = str(uuid.uuid4())
+
+ self.assertRaises(exceptions.BadRequest,
+ self.client.create_flavor,
+ invalid_flavor_name,
+ self.ram, self.vcpus,
+ self.disk,
+ new_flavor_id,
+ ephemeral=self.ephemeral,
+ swap=self.swap,
+ rxtx=self.rxtx,
+ is_public='False')
+
+ @test.attr(type=['negative', 'gate'])
+ def test_create_flavor_with_invalid_flavor_id(self):
+ # the regex of flavor_id is '^[\w\.\- ]*$', and it cannot contain
+ # leading and/or trailing whitespace
+ new_flavor_name = data_utils.rand_name(self.flavor_name_prefix)
+ invalid_flavor_id = '!@#$%'
+
+ self.assertRaises(exceptions.BadRequest,
+ self.client.create_flavor,
+ new_flavor_name,
+ self.ram, self.vcpus,
+ self.disk,
+ invalid_flavor_id,
+ ephemeral=self.ephemeral,
+ swap=self.swap,
+ rxtx=self.rxtx,
+ is_public='False')
+
+ @test.attr(type=['negative', 'gate'])
+ def test_create_flavor_with_id_length_exceeds_255(self):
+ # the length of flavor_id should not exceed 255 characters
+ new_flavor_name = data_utils.rand_name(self.flavor_name_prefix)
+ invalid_flavor_id = 'a' * 256
+
+ self.assertRaises(exceptions.BadRequest,
+ self.client.create_flavor,
+ new_flavor_name,
+ self.ram, self.vcpus,
+ self.disk,
+ invalid_flavor_id,
+ ephemeral=self.ephemeral,
+ swap=self.swap,
+ rxtx=self.rxtx,
+ is_public='False')
+
+ @test.attr(type=['negative', 'gate'])
+ def test_create_flavor_with_invalid_root_gb(self):
+ # root_gb attribute should be non-negative ( >= 0) integer
+ new_flavor_name = data_utils.rand_name(self.flavor_name_prefix)
+ new_flavor_id = str(uuid.uuid4())
+
+ self.assertRaises(exceptions.BadRequest,
+ self.client.create_flavor,
+ new_flavor_name,
+ self.ram, self.vcpus,
+ -1,
+ new_flavor_id,
+ ephemeral=self.ephemeral,
+ swap=self.swap,
+ rxtx=self.rxtx,
+ is_public='False')
+
+ @test.attr(type=['negative', 'gate'])
+ def test_create_flavor_with_invalid_ephemeral_gb(self):
+ # ephemeral_gb attribute should be non-negative ( >= 0) integer
+ new_flavor_name = data_utils.rand_name(self.flavor_name_prefix)
+ new_flavor_id = str(uuid.uuid4())
+
+ self.assertRaises(exceptions.BadRequest,
+ self.client.create_flavor,
+ new_flavor_name,
+ self.ram, self.vcpus,
+ self.disk,
+ new_flavor_id,
+ ephemeral=-1,
+ swap=self.swap,
+ rxtx=self.rxtx,
+ is_public='False')
+
+ @test.attr(type=['negative', 'gate'])
+ def test_create_flavor_with_invalid_swap(self):
+ # swap attribute should be non-negative ( >= 0) integer
+ new_flavor_name = data_utils.rand_name(self.flavor_name_prefix)
+ new_flavor_id = str(uuid.uuid4())
+
+ self.assertRaises(exceptions.BadRequest,
+ self.client.create_flavor,
+ new_flavor_name,
+ self.ram, self.vcpus,
+ self.disk,
+ new_flavor_id,
+ ephemeral=self.ephemeral,
+ swap=-1,
+ rxtx=self.rxtx,
+ is_public='False')
+
+ @test.attr(type=['negative', 'gate'])
+ def test_create_flavor_with_invalid_rxtx_factor(self):
+ # rxtx_factor attribute should be a positive float
+ new_flavor_name = data_utils.rand_name(self.flavor_name_prefix)
+ new_flavor_id = str(uuid.uuid4())
+
+ self.assertRaises(exceptions.BadRequest,
+ self.client.create_flavor,
+ new_flavor_name,
+ self.ram, self.vcpus,
+ self.disk,
+ new_flavor_id,
+ ephemeral=self.ephemeral,
+ swap=self.swap,
+ rxtx=-1.5,
+ is_public='False')
+
+ @test.attr(type=['negative', 'gate'])
+ def test_create_flavor_with_invalid_is_public(self):
+ # is_public attribute should be boolean
+ new_flavor_name = data_utils.rand_name(self.flavor_name_prefix)
+ new_flavor_id = str(uuid.uuid4())
+
+ self.assertRaises(exceptions.BadRequest,
+ self.client.create_flavor,
+ new_flavor_name,
+ self.ram, self.vcpus,
+ self.disk,
+ new_flavor_id,
+ ephemeral=self.ephemeral,
+ swap=self.swap,
+ rxtx=self.rxtx,
+ is_public='Invalid')
+
+ @test.attr(type=['negative', 'gate'])
+ def test_create_flavor_already_exists(self):
+ flavor_name = data_utils.rand_name(self.flavor_name_prefix)
+ new_flavor_id = str(uuid.uuid4())
+
+ resp, flavor = self.client.create_flavor(flavor_name,
+ self.ram, self.vcpus,
+ self.disk,
+ new_flavor_id,
+ ephemeral=self.ephemeral,
+ swap=self.swap,
+ rxtx=self.rxtx)
+ self.assertEqual(200, resp.status)
+ self.addCleanup(self.flavor_clean_up, flavor['id'])
+
+ self.assertRaises(exceptions.Conflict,
+ self.client.create_flavor,
+ flavor_name,
+ self.ram, self.vcpus,
+ self.disk,
+ new_flavor_id,
+ ephemeral=self.ephemeral,
+ swap=self.swap,
+ rxtx=self.rxtx)
+
+ @test.attr(type=['negative', 'gate'])
+ def test_delete_nonexistent_flavor(self):
+ nonexistent_flavor_id = str(uuid.uuid4())
+
+ self.assertRaises(exceptions.NotFound,
+ self.client.delete_flavor,
+ nonexistent_flavor_id)
diff --git a/tempest/api/compute/admin/test_hosts.py b/tempest/api/compute/admin/test_hosts.py
index b4b3139..e612566 100644
--- a/tempest/api/compute/admin/test_hosts.py
+++ b/tempest/api/compute/admin/test_hosts.py
@@ -14,7 +14,7 @@
from tempest.api.compute import base
from tempest.common import tempest_fixtures as fixtures
-from tempest.test import attr
+from tempest import test
class HostsAdminTestJSON(base.BaseV2ComputeAdminTest):
@@ -28,13 +28,13 @@
super(HostsAdminTestJSON, cls).setUpClass()
cls.client = cls.os_adm.hosts_client
- @attr(type='gate')
+ @test.attr(type='gate')
def test_list_hosts(self):
resp, hosts = self.client.list_hosts()
self.assertEqual(200, resp.status)
self.assertTrue(len(hosts) >= 2, str(hosts))
- @attr(type='gate')
+ @test.attr(type='gate')
def test_list_hosts_with_zone(self):
self.useFixture(fixtures.LockFixture('availability_zone'))
resp, hosts = self.client.list_hosts()
@@ -46,7 +46,7 @@
self.assertTrue(len(hosts) >= 1)
self.assertIn(host, hosts)
- @attr(type='gate')
+ @test.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
@@ -55,7 +55,7 @@
self.assertNotEqual(0, len(hosts))
self.assertEqual(200, resp.status)
- @attr(type='gate')
+ @test.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
@@ -64,7 +64,7 @@
self.assertEqual(0, len(hosts))
self.assertEqual(200, resp.status)
- @attr(type='gate')
+ @test.attr(type='gate')
def test_show_host_detail(self):
resp, hosts = self.client.list_hosts()
self.assertEqual(200, resp.status)
diff --git a/tempest/api/compute/admin/test_hypervisor.py b/tempest/api/compute/admin/test_hypervisor.py
index 4e1b289..48f9ffb 100644
--- a/tempest/api/compute/admin/test_hypervisor.py
+++ b/tempest/api/compute/admin/test_hypervisor.py
@@ -14,7 +14,7 @@
# under the License.
from tempest.api.compute import base
-from tempest.test import attr
+from tempest import test
class HypervisorAdminTestJSON(base.BaseV2ComputeAdminTest):
@@ -37,20 +37,20 @@
def assertHypervisors(self, hypers):
self.assertTrue(len(hypers) > 0, "No hypervisors found: %s" % hypers)
- @attr(type='gate')
+ @test.attr(type='gate')
def test_get_hypervisor_list(self):
# List of hypervisor and available hypervisors hostname
hypers = self._list_hypervisors()
self.assertHypervisors(hypers)
- @attr(type='gate')
+ @test.attr(type='gate')
def test_get_hypervisor_list_details(self):
# Display the details of the all hypervisor
resp, hypers = self.client.get_hypervisor_list_details()
self.assertEqual(200, resp.status)
self.assertHypervisors(hypers)
- @attr(type='gate')
+ @test.attr(type='gate')
def test_get_hypervisor_show_details(self):
# Display the details of the specified hypervisor
hypers = self._list_hypervisors()
@@ -63,7 +63,7 @@
self.assertEqual(details['hypervisor_hostname'],
hypers[0]['hypervisor_hostname'])
- @attr(type='gate')
+ @test.attr(type='gate')
def test_get_hypervisor_show_servers(self):
# Show instances about the specific hypervisors
hypers = self._list_hypervisors()
@@ -74,14 +74,14 @@
self.assertEqual(200, resp.status)
self.assertTrue(len(hypervisors) > 0)
- @attr(type='gate')
+ @test.attr(type='gate')
def test_get_hypervisor_stats(self):
# Verify the stats of the all hypervisor
resp, stats = self.client.get_hypervisor_stats()
self.assertEqual(200, resp.status)
self.assertTrue(len(stats) > 0)
- @attr(type='gate')
+ @test.attr(type='gate')
def test_get_hypervisor_uptime(self):
# Verify that GET shows the specified hypervisor uptime
hypers = self._list_hypervisors()
@@ -101,7 +101,7 @@
has_valid_uptime,
"None of the hypervisors had a valid uptime: %s" % hypers)
- @attr(type='gate')
+ @test.attr(type='gate')
def test_search_hypervisor(self):
hypers = self._list_hypervisors()
self.assertHypervisors(hypers)
diff --git a/tempest/api/compute/admin/test_hypervisor_negative.py b/tempest/api/compute/admin/test_hypervisor_negative.py
index be0153b..4ba8d30 100644
--- a/tempest/api/compute/admin/test_hypervisor_negative.py
+++ b/tempest/api/compute/admin/test_hypervisor_negative.py
@@ -18,7 +18,7 @@
from tempest.api.compute import base
from tempest.common.utils import data_utils
from tempest import exceptions
-from tempest.test import attr
+from tempest import test
class HypervisorAdminNegativeTestJSON(base.BaseV2ComputeAdminTest):
@@ -39,7 +39,7 @@
self.assertEqual(200, resp.status)
return hypers
- @attr(type=['negative', 'gate'])
+ @test.attr(type=['negative', 'gate'])
def test_show_nonexistent_hypervisor(self):
nonexistent_hypervisor_id = str(uuid.uuid4())
@@ -48,7 +48,7 @@
self.client.get_hypervisor_show_details,
nonexistent_hypervisor_id)
- @attr(type=['negative', 'gate'])
+ @test.attr(type=['negative', 'gate'])
def test_show_hypervisor_with_non_admin_user(self):
hypers = self._list_hypervisors()
self.assertTrue(len(hypers) > 0)
@@ -58,7 +58,7 @@
self.non_adm_client.get_hypervisor_show_details,
hypers[0]['id'])
- @attr(type=['negative', 'gate'])
+ @test.attr(type=['negative', 'gate'])
def test_show_servers_with_non_admin_user(self):
hypers = self._list_hypervisors()
self.assertTrue(len(hypers) > 0)
@@ -68,7 +68,7 @@
self.non_adm_client.get_hypervisor_servers,
hypers[0]['id'])
- @attr(type=['negative', 'gate'])
+ @test.attr(type=['negative', 'gate'])
def test_show_servers_with_nonexistent_hypervisor(self):
nonexistent_hypervisor_id = str(uuid.uuid4())
@@ -77,13 +77,13 @@
self.client.get_hypervisor_servers,
nonexistent_hypervisor_id)
- @attr(type=['negative', 'gate'])
+ @test.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'])
+ @test.attr(type=['negative', 'gate'])
def test_get_nonexistent_hypervisor_uptime(self):
nonexistent_hypervisor_id = str(uuid.uuid4())
@@ -92,7 +92,7 @@
self.client.get_hypervisor_uptime,
nonexistent_hypervisor_id)
- @attr(type=['negative', 'gate'])
+ @test.attr(type=['negative', 'gate'])
def test_get_hypervisor_uptime_with_non_admin_user(self):
hypers = self._list_hypervisors()
self.assertTrue(len(hypers) > 0)
@@ -102,21 +102,21 @@
self.non_adm_client.get_hypervisor_uptime,
hypers[0]['id'])
- @attr(type=['negative', 'gate'])
+ @test.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'])
+ @test.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'])
+ @test.attr(type=['negative', 'gate'])
def test_search_nonexistent_hypervisor(self):
nonexistent_hypervisor_name = data_utils.rand_name('test_hypervisor')
@@ -125,7 +125,7 @@
self.client.search_hypervisor,
nonexistent_hypervisor_name)
- @attr(type=['negative', 'gate'])
+ @test.attr(type=['negative', 'gate'])
def test_search_hypervisor_with_non_admin_user(self):
hypers = self._list_hypervisors()
self.assertTrue(len(hypers) > 0)
diff --git a/tempest/api/compute/admin/test_instance_usage_audit_log.py b/tempest/api/compute/admin/test_instance_usage_audit_log.py
index 13f504f..055a177 100644
--- a/tempest/api/compute/admin/test_instance_usage_audit_log.py
+++ b/tempest/api/compute/admin/test_instance_usage_audit_log.py
@@ -14,10 +14,10 @@
# under the License.
import datetime
+import urllib
from tempest.api.compute import base
-from tempest.test import attr
-import urllib
+from tempest import test
class InstanceUsageAuditLogTestJSON(base.BaseV2ComputeAdminTest):
@@ -27,7 +27,7 @@
super(InstanceUsageAuditLogTestJSON, cls).setUpClass()
cls.adm_client = cls.os_adm.instance_usages_audit_log_client
- @attr(type='gate')
+ @test.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()
@@ -40,7 +40,7 @@
for item in expected_items:
self.assertIn(item, body)
- @attr(type='gate')
+ @test.attr(type='gate')
def test_get_instance_usage_audit_log(self):
# Get instance usage audit log before specified time
now = datetime.datetime.now()
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
index e128d0c..6a5fc96 100644
--- a/tempest/api/compute/admin/test_instance_usage_audit_log_negative.py
+++ b/tempest/api/compute/admin/test_instance_usage_audit_log_negative.py
@@ -14,11 +14,11 @@
# under the License.
import datetime
+import urllib
from tempest.api.compute import base
from tempest import exceptions
-from tempest.test import attr
-import urllib
+from tempest import test
class InstanceUsageAuditLogNegativeTestJSON(base.BaseV2ComputeAdminTest):
@@ -28,7 +28,7 @@
super(InstanceUsageAuditLogNegativeTestJSON, cls).setUpClass()
cls.adm_client = cls.os_adm.instance_usages_audit_log_client
- @attr(type=['negative', 'gate'])
+ @test.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,
@@ -40,7 +40,7 @@
get_instance_usage_audit_log,
urllib.quote(now.strftime("%Y-%m-%d %H:%M:%S")))
- @attr(type=['negative', 'gate'])
+ @test.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,
diff --git a/tempest/api/compute/admin/test_quotas.py b/tempest/api/compute/admin/test_quotas.py
index 5af091e..09c7274 100644
--- a/tempest/api/compute/admin/test_quotas.py
+++ b/tempest/api/compute/admin/test_quotas.py
@@ -92,6 +92,29 @@
self.assertEqual(200, resp.status)
self.assertEqual(quota_set['ram'], 5120)
+ @test.attr(type='gate')
+ def test_delete_quota(self):
+ # Admin can delete the resource quota set for a tenant
+ tenant_name = data_utils.rand_name('ram_quota_tenant_')
+ tenant_desc = tenant_name + '-desc'
+ identity_client = self.os_adm.identity_client
+ _, tenant = identity_client.create_tenant(name=tenant_name,
+ description=tenant_desc)
+ tenant_id = tenant['id']
+ self.addCleanup(identity_client.delete_tenant, tenant_id)
+ resp, quota_set_default = self.adm_client.get_quota_set(tenant_id)
+ ram_default = quota_set_default['ram']
+
+ resp, body = self.adm_client.update_quota_set(tenant_id, ram='5120')
+ self.assertEqual(200, resp.status)
+
+ resp, body = self.adm_client.delete_quota_set(tenant_id)
+ self.assertEqual(202, resp.status)
+
+ resp, quota_set_new = self.adm_client.get_quota_set(tenant_id)
+ self.assertEqual(200, resp.status)
+ self.assertEqual(ram_default, quota_set_new['ram'])
+
class QuotasAdminTestXML(QuotasAdminTestJSON):
_interface = 'xml'
diff --git a/tempest/api/compute/admin/test_security_groups.py b/tempest/api/compute/admin/test_security_groups.py
index 69ba2c2..f728d68 100644
--- a/tempest/api/compute/admin/test_security_groups.py
+++ b/tempest/api/compute/admin/test_security_groups.py
@@ -18,7 +18,7 @@
from tempest.api.compute import base
from tempest.common.utils import data_utils
from tempest import config
-from tempest.test import attr
+from tempest import test
CONF = config.CONF
@@ -42,7 +42,7 @@
@testtools.skipIf(CONF.service_available.neutron,
"Skipped because neutron do not support all_tenants"
"search filter.")
- @attr(type='smoke')
+ @test.attr(type='smoke')
def test_list_security_groups_list_all_tenants_filter(self):
# Admin can list security groups of all tenants
# List of all security groups created
diff --git a/tempest/api/compute/admin/test_servers.py b/tempest/api/compute/admin/test_servers.py
index 40a4df7..1f2ddf4 100644
--- a/tempest/api/compute/admin/test_servers.py
+++ b/tempest/api/compute/admin/test_servers.py
@@ -27,6 +27,7 @@
_host_key = 'OS-EXT-SRV-ATTR:host'
@classmethod
+ @test.safe_setup
def setUpClass(cls):
super(ServersAdminTestJSON, cls).setUpClass()
cls.client = cls.os_adm.servers_client
@@ -184,6 +185,16 @@
resp, server_body = self.client.inject_network_info(server['id'])
self.assertEqual(202, resp.status)
+ @test.attr(type='gate')
+ def test_create_server_with_scheduling_hint(self):
+ # Create a server with scheduler hints.
+ hints = {
+ 'same_host': self.s1_id
+ }
+ resp, server = self.create_test_server(sched_hints=hints,
+ wait_until='ACTIVE')
+ self.assertEqual('202', resp['status'])
+
class ServersAdminTestXML(ServersAdminTestJSON):
_host_key = (
diff --git a/tempest/api/compute/admin/test_servers_negative.py b/tempest/api/compute/admin/test_servers_negative.py
index dff4aaa..797b780 100644
--- a/tempest/api/compute/admin/test_servers_negative.py
+++ b/tempest/api/compute/admin/test_servers_negative.py
@@ -17,7 +17,7 @@
from tempest.api.compute import base
from tempest.common.utils import data_utils
from tempest import exceptions
-from tempest.test import attr
+from tempest import test
class ServersAdminNegativeTestJSON(base.BaseV2ComputeAdminTest):
@@ -52,7 +52,7 @@
flavor_id = data_utils.rand_int_id(start=1000)
return flavor_id
- @attr(type=['negative', 'gate'])
+ @test.attr(type=['negative', 'gate'])
def test_resize_server_using_overlimit_ram(self):
flavor_name = data_utils.rand_name("flavor-")
flavor_id = self._get_unused_flavor_id()
@@ -70,7 +70,7 @@
self.servers[0]['id'],
flavor_ref['id'])
- @attr(type=['negative', 'gate'])
+ @test.attr(type=['negative', 'gate'])
def test_resize_server_using_overlimit_vcpus(self):
flavor_name = data_utils.rand_name("flavor-")
flavor_id = self._get_unused_flavor_id()
@@ -88,38 +88,38 @@
self.servers[0]['id'],
flavor_ref['id'])
- @attr(type=['negative', 'gate'])
+ @test.attr(type=['negative', 'gate'])
def test_reset_state_server_invalid_state(self):
self.assertRaises(exceptions.BadRequest,
self.client.reset_state, self.s1_id,
state='invalid')
- @attr(type=['negative', 'gate'])
+ @test.attr(type=['negative', 'gate'])
def test_reset_state_server_invalid_type(self):
self.assertRaises(exceptions.BadRequest,
self.client.reset_state, self.s1_id,
state=1)
- @attr(type=['negative', 'gate'])
+ @test.attr(type=['negative', 'gate'])
def test_reset_state_server_nonexistent_server(self):
self.assertRaises(exceptions.NotFound,
self.client.reset_state, '999')
- @attr(type=['negative', 'gate'])
+ @test.attr(type=['negative', 'gate'])
def test_get_server_diagnostics_by_non_admin(self):
# Non-admin user can not view server diagnostics according to policy
self.assertRaises(exceptions.Unauthorized,
self.non_adm_client.get_server_diagnostics,
self.s1_id)
- @attr(type=['negative', 'gate'])
+ @test.attr(type=['negative', 'gate'])
def test_migrate_non_existent_server(self):
# migrate a non existent server
self.assertRaises(exceptions.NotFound,
self.client.migrate_server,
str(uuid.uuid4()))
- @attr(type=['negative', 'gate'])
+ @test.attr(type=['negative', 'gate'])
def test_migrate_server_invalid_state(self):
# create server.
resp, server = self.create_test_server(wait_until='ACTIVE')
diff --git a/tempest/api/compute/admin/test_services.py b/tempest/api/compute/admin/test_services.py
index 3e45d65..2feb825 100644
--- a/tempest/api/compute/admin/test_services.py
+++ b/tempest/api/compute/admin/test_services.py
@@ -15,7 +15,7 @@
# under the License.
from tempest.api.compute import base
-from tempest.test import attr
+from tempest import test
class ServicesAdminTestJSON(base.BaseV2ComputeAdminTest):
@@ -29,13 +29,13 @@
super(ServicesAdminTestJSON, cls).setUpClass()
cls.client = cls.os_adm.services_client
- @attr(type='gate')
+ @test.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='gate')
+ @test.attr(type='gate')
def test_get_service_by_service_binary_name(self):
binary_name = 'nova-compute'
params = {'binary': binary_name}
@@ -45,13 +45,14 @@
for service in services:
self.assertEqual(binary_name, service['binary'])
- @attr(type='gate')
+ @test.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
@@ -63,12 +64,13 @@
# on order.
self.assertEqual(sorted(s1), sorted(s2))
- @attr(type='gate')
+ @test.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))
diff --git a/tempest/api/compute/admin/test_services_negative.py b/tempest/api/compute/admin/test_services_negative.py
index a4bf754..c78d70d 100644
--- a/tempest/api/compute/admin/test_services_negative.py
+++ b/tempest/api/compute/admin/test_services_negative.py
@@ -14,7 +14,7 @@
from tempest.api.compute import base
from tempest import exceptions
-from tempest.test import attr
+from tempest import test
class ServicesAdminNegativeTestJSON(base.BaseV2ComputeAdminTest):
@@ -29,12 +29,12 @@
cls.client = cls.os_adm.services_client
cls.non_admin_client = cls.services_client
- @attr(type=['negative', 'gate'])
+ @test.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=['negative', 'gate'])
+ @test.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()
@@ -43,7 +43,7 @@
self.assertEqual(200, resp.status)
self.assertEqual(len(services), len(services_xxx))
- @attr(type=['negative', 'gate'])
+ @test.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']
@@ -52,7 +52,7 @@
self.assertEqual(200, resp.status)
self.assertEqual(0, len(services))
- @attr(type=['negative', 'gate'])
+ @test.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']
diff --git a/tempest/api/compute/admin/test_simple_tenant_usage.py b/tempest/api/compute/admin/test_simple_tenant_usage.py
index 2f1391b..33cd6f3 100644
--- a/tempest/api/compute/admin/test_simple_tenant_usage.py
+++ b/tempest/api/compute/admin/test_simple_tenant_usage.py
@@ -14,10 +14,10 @@
# under the License.
import datetime
+import time
from tempest.api.compute import base
-from tempest.test import attr
-import time
+from tempest import test
class TenantUsagesTestJSON(base.BaseV2ComputeAdminTest):
@@ -46,7 +46,7 @@
# Returns formatted datetime
return at.strftime('%Y-%m-%dT%H:%M:%S.%f')
- @attr(type='gate')
+ @test.attr(type='gate')
def test_list_usage_all_tenants(self):
# Get usage for all tenants
params = {'start': self.start,
@@ -56,7 +56,7 @@
self.assertEqual(200, resp.status)
self.assertEqual(len(tenant_usage), 8)
- @attr(type='gate')
+ @test.attr(type='gate')
def test_get_usage_tenant(self):
# Get usage for a specific tenant
params = {'start': self.start,
@@ -67,7 +67,7 @@
self.assertEqual(200, resp.status)
self.assertEqual(len(tenant_usage), 8)
- @attr(type='gate')
+ @test.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,
diff --git a/tempest/api/compute/admin/test_simple_tenant_usage_negative.py b/tempest/api/compute/admin/test_simple_tenant_usage_negative.py
index 4ad3e1a..a080f2e 100644
--- a/tempest/api/compute/admin/test_simple_tenant_usage_negative.py
+++ b/tempest/api/compute/admin/test_simple_tenant_usage_negative.py
@@ -17,7 +17,7 @@
from tempest.api.compute import base
from tempest import exceptions
-from tempest.test import attr
+from tempest import test
class TenantUsagesNegativeTestJSON(base.BaseV2ComputeAdminTest):
@@ -37,7 +37,7 @@
# Returns formatted datetime
return at.strftime('%Y-%m-%dT%H:%M:%S.%f')
- @attr(type=['negative', 'gate'])
+ @test.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,
@@ -46,7 +46,7 @@
self.adm_client.get_tenant_usage,
'', params)
- @attr(type=['negative', 'gate'])
+ @test.attr(type=['negative', 'gate'])
def test_get_usage_tenant_with_invalid_date(self):
# Get usage for tenant with invalid date
params = {'start': self.end,
@@ -58,7 +58,7 @@
self.adm_client.get_tenant_usage,
tenant_id, params)
- @attr(type=['negative', 'gate'])
+ @test.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,
diff --git a/tempest/api/compute/base.py b/tempest/api/compute/base.py
index 28d50c9..abd36a6 100644
--- a/tempest/api/compute/base.py
+++ b/tempest/api/compute/base.py
@@ -315,14 +315,14 @@
"%s will be removed shortly" % cls.__name__)
raise cls.skipException(skip_msg)
- cls.set_network_resources()
- super(BaseV3ComputeTest, cls).setUpClass()
if not CONF.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.set_network_resources()
+ super(BaseV3ComputeTest, cls).setUpClass()
+
cls.servers_client = cls.os.servers_v3_client
cls.images_client = cls.os.image_client
cls.flavors_client = cls.os.flavors_v3_client
@@ -411,3 +411,4 @@
cls.aggregates_admin_client = cls.os_adm.aggregates_v3_client
cls.hosts_admin_client = cls.os_adm.hosts_v3_client
cls.quotas_admin_client = cls.os_adm.quotas_v3_client
+ cls.agents_admin_client = cls.os_adm.agents_v3_client
diff --git a/tempest/api/compute/certificates/test_certificates.py b/tempest/api/compute/certificates/test_certificates.py
index 01fdc7c..5299d13 100644
--- a/tempest/api/compute/certificates/test_certificates.py
+++ b/tempest/api/compute/certificates/test_certificates.py
@@ -14,12 +14,12 @@
# under the License.
from tempest.api.compute import base
-from tempest.test import attr
+from tempest import test
class CertificatesTestJSON(base.BaseV2ComputeTest):
- @attr(type='gate')
+ @test.attr(type='gate')
def test_create_and_get_root_certificate(self):
# create certificates
resp, create_body = self.certificates_client.create_certificate()
diff --git a/tempest/api/compute/flavors/test_flavors.py b/tempest/api/compute/flavors/test_flavors.py
index 98a8e29..6e202f6 100644
--- a/tempest/api/compute/flavors/test_flavors.py
+++ b/tempest/api/compute/flavors/test_flavors.py
@@ -14,7 +14,7 @@
# under the License.
from tempest.api.compute import base
-from tempest.test import attr
+from tempest import test
class FlavorsTestJSON(base.BaseV2ComputeTest):
@@ -24,7 +24,7 @@
super(FlavorsTestJSON, cls).setUpClass()
cls.client = cls.flavors_client
- @attr(type='smoke')
+ @test.attr(type='smoke')
def test_list_flavors(self):
# List of all flavors should contain the expected flavor
resp, flavors = self.client.list_flavors()
@@ -33,34 +33,34 @@
'name': flavor['name']}
self.assertIn(flavor_min_detail, flavors)
- @attr(type='smoke')
+ @test.attr(type='smoke')
def test_list_flavors_with_detail(self):
# Detailed list of all flavors should contain the expected flavor
resp, flavors = self.client.list_flavors_with_detail()
resp, flavor = self.client.get_flavor_details(self.flavor_ref)
self.assertIn(flavor, flavors)
- @attr(type='smoke')
+ @test.attr(type='smoke')
def test_get_flavor(self):
# The expected flavor details should be returned
resp, flavor = self.client.get_flavor_details(self.flavor_ref)
self.assertEqual(self.flavor_ref, flavor['id'])
- @attr(type='gate')
+ @test.attr(type='gate')
def test_list_flavors_limit_results(self):
# Only the expected number of flavors should be returned
params = {'limit': 1}
resp, flavors = self.client.list_flavors(params)
self.assertEqual(1, len(flavors))
- @attr(type='gate')
+ @test.attr(type='gate')
def test_list_flavors_detailed_limit_results(self):
# Only the expected number of flavors (detailed) should be returned
params = {'limit': 1}
resp, flavors = self.client.list_flavors_with_detail(params)
self.assertEqual(1, len(flavors))
- @attr(type='gate')
+ @test.attr(type='gate')
def test_list_flavors_using_marker(self):
# The list of flavors should start from the provided marker
resp, flavors = self.client.list_flavors()
@@ -71,7 +71,7 @@
self.assertFalse(any([i for i in flavors if i['id'] == flavor_id]),
'The list of flavors did not start after the marker.')
- @attr(type='gate')
+ @test.attr(type='gate')
def test_list_flavors_detailed_using_marker(self):
# The list of flavors should start from the provided marker
resp, flavors = self.client.list_flavors_with_detail()
@@ -82,7 +82,7 @@
self.assertFalse(any([i for i in flavors if i['id'] == flavor_id]),
'The list of flavors did not start after the marker.')
- @attr(type='gate')
+ @test.attr(type='gate')
def test_list_flavors_detailed_filter_by_min_disk(self):
# The detailed list of flavors should be filtered by disk space
resp, flavors = self.client.list_flavors_with_detail()
@@ -93,7 +93,7 @@
resp, flavors = self.client.list_flavors_with_detail(params)
self.assertFalse(any([i for i in flavors if i['id'] == flavor_id]))
- @attr(type='gate')
+ @test.attr(type='gate')
def test_list_flavors_detailed_filter_by_min_ram(self):
# The detailed list of flavors should be filtered by RAM
resp, flavors = self.client.list_flavors_with_detail()
@@ -104,7 +104,7 @@
resp, flavors = self.client.list_flavors_with_detail(params)
self.assertFalse(any([i for i in flavors if i['id'] == flavor_id]))
- @attr(type='gate')
+ @test.attr(type='gate')
def test_list_flavors_filter_by_min_disk(self):
# The list of flavors should be filtered by disk space
resp, flavors = self.client.list_flavors_with_detail()
@@ -115,7 +115,7 @@
resp, flavors = self.client.list_flavors(params)
self.assertFalse(any([i for i in flavors if i['id'] == flavor_id]))
- @attr(type='gate')
+ @test.attr(type='gate')
def test_list_flavors_filter_by_min_ram(self):
# The list of flavors should be filtered by RAM
resp, flavors = self.client.list_flavors_with_detail()
diff --git a/tempest/api/compute/flavors/test_flavors_negative_xml.py b/tempest/api/compute/flavors/test_flavors_negative_xml.py
index c93c7c9..bf73c0e 100644
--- a/tempest/api/compute/flavors/test_flavors_negative_xml.py
+++ b/tempest/api/compute/flavors/test_flavors_negative_xml.py
@@ -17,7 +17,7 @@
from tempest.api.compute import base
from tempest import exceptions
-from tempest.test import attr
+from tempest import test
class FlavorsNegativeTestXML(base.BaseV2ComputeTest):
@@ -28,19 +28,19 @@
super(FlavorsNegativeTestXML, cls).setUpClass()
cls.client = cls.flavors_client
- @attr(type=['negative', 'gate'])
+ @test.attr(type=['negative', 'gate'])
def test_invalid_minRam_filter(self):
self.assertRaises(exceptions.BadRequest,
self.client.list_flavors_with_detail,
{'minRam': 'invalid'})
- @attr(type=['negative', 'gate'])
+ @test.attr(type=['negative', 'gate'])
def test_invalid_minDisk_filter(self):
self.assertRaises(exceptions.BadRequest,
self.client.list_flavors_with_detail,
{'minDisk': 'invalid'})
- @attr(type=['negative', 'gate'])
+ @test.attr(type=['negative', 'gate'])
def test_non_existent_flavor_id(self):
# flavor details are not returned for non-existent flavors
nonexistent_flavor_id = str(uuid.uuid4())
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 c0f7af0..abd8a4c 100644
--- a/tempest/api/compute/floating_ips/test_floating_ips_actions.py
+++ b/tempest/api/compute/floating_ips/test_floating_ips_actions.py
@@ -24,10 +24,11 @@
floating_ip = None
@classmethod
+ @test.safe_setup
def setUpClass(cls):
super(FloatingIPsTestJSON, cls).setUpClass()
cls.client = cls.floating_ips_client
- #cls.servers_client = cls.servers_client
+ cls.floating_ip_id = None
# Server creation
resp, server = cls.create_test_server(wait_until='ACTIVE')
@@ -40,7 +41,8 @@
@classmethod
def tearDownClass(cls):
# Deleting the floating IP which is created in this method
- resp, body = cls.client.delete_floating_ip(cls.floating_ip_id)
+ if cls.floating_ip_id:
+ resp, body = cls.client.delete_floating_ip(cls.floating_ip_id)
super(FloatingIPsTestJSON, cls).tearDownClass()
@test.attr(type='gate')
diff --git a/tempest/api/compute/floating_ips/test_floating_ips_actions_negative.py b/tempest/api/compute/floating_ips/test_floating_ips_actions_negative.py
index 0c3663e..9fc43e2 100644
--- a/tempest/api/compute/floating_ips/test_floating_ips_actions_negative.py
+++ b/tempest/api/compute/floating_ips/test_floating_ips_actions_negative.py
@@ -19,7 +19,7 @@
from tempest.common.utils import data_utils
from tempest import config
from tempest import exceptions
-from tempest.test import attr
+from tempest import test
CONF = config.CONF
@@ -47,7 +47,7 @@
if cls.non_exist_id not in cls.floating_ip_ids:
break
- @attr(type=['negative', 'gate'])
+ @test.attr(type=['negative', 'gate'])
def test_allocate_floating_ip_from_nonexistent_pool(self):
# Negative test:Allocation of a new floating IP from a nonexistent_pool
# to a project should fail
@@ -55,7 +55,7 @@
self.client.create_floating_ip,
"non_exist_pool")
- @attr(type=['negative', 'gate'])
+ @test.attr(type=['negative', 'gate'])
def test_delete_nonexistent_floating_ip(self):
# Negative test:Deletion of a nonexistent floating IP
# from project should fail
@@ -64,7 +64,7 @@
self.assertRaises(exceptions.NotFound, self.client.delete_floating_ip,
self.non_exist_id)
- @attr(type=['negative', 'gate'])
+ @test.attr(type=['negative', 'gate'])
def test_associate_nonexistent_floating_ip(self):
# Negative test:Association of a non existent floating IP
# to specific server should fail
@@ -73,7 +73,7 @@
self.client.associate_floating_ip_to_server,
"0.0.0.0", self.server_id)
- @attr(type=['negative', 'gate'])
+ @test.attr(type=['negative', 'gate'])
def test_dissociate_nonexistent_floating_ip(self):
# Negative test:Dissociation of a non existent floating IP should fail
# Dissociating non existent floating IP
@@ -81,7 +81,7 @@
self.client.disassociate_floating_ip_from_server,
"0.0.0.0", self.server_id)
- @attr(type=['negative', 'gate'])
+ @test.attr(type=['negative', 'gate'])
def test_associate_ip_to_server_without_passing_floating_ip(self):
# Negative test:Association of empty floating IP to specific server
# should raise NotFound exception
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 d69e33c..94dcf61 100644
--- a/tempest/api/compute/floating_ips/test_list_floating_ips.py
+++ b/tempest/api/compute/floating_ips/test_list_floating_ips.py
@@ -14,7 +14,7 @@
# under the License.
from tempest.api.compute import base
-from tempest.test import attr
+from tempest import test
class FloatingIPDetailsTestJSON(base.BaseV2ComputeTest):
@@ -36,7 +36,7 @@
cls.client.delete_floating_ip(cls.floating_ip_id[i])
super(FloatingIPDetailsTestJSON, cls).tearDownClass()
- @attr(type='gate')
+ @test.attr(type='gate')
def test_list_floating_ips(self):
# Positive test:Should return the list of floating IPs
resp, body = self.client.list_floating_ips()
@@ -47,7 +47,7 @@
for i in range(3):
self.assertIn(self.floating_ip[i], floating_ips)
- @attr(type='gate')
+ @test.attr(type='gate')
def test_get_floating_ip_details(self):
# Positive test:Should be able to GET the details of floatingIP
# Creating a floating IP for which details are to be checked
@@ -69,7 +69,7 @@
body['fixed_ip'])
self.assertEqual(floating_ip_id, body['id'])
- @attr(type='gate')
+ @test.attr(type='gate')
def test_list_floating_ip_pools(self):
# Positive test:Should return the list of floating IP Pools
resp, floating_ip_pools = self.client.list_floating_ip_pools()
diff --git a/tempest/api/compute/floating_ips/test_list_floating_ips_negative.py b/tempest/api/compute/floating_ips/test_list_floating_ips_negative.py
index 5701be8..8cb2f08 100644
--- a/tempest/api/compute/floating_ips/test_list_floating_ips_negative.py
+++ b/tempest/api/compute/floating_ips/test_list_floating_ips_negative.py
@@ -19,7 +19,7 @@
from tempest.common.utils import data_utils
from tempest import config
from tempest import exceptions
-from tempest.test import attr
+from tempest import test
CONF = config.CONF
@@ -31,7 +31,7 @@
super(FloatingIPDetailsNegativeTestJSON, cls).setUpClass()
cls.client = cls.floating_ips_client
- @attr(type=['negative', 'gate'])
+ @test.attr(type=['negative', 'gate'])
def test_get_nonexistent_floating_ip_details(self):
# Negative test:Should not be able to GET the details
# of non-existent floating IP
diff --git a/tempest/api/compute/images/test_image_metadata.py b/tempest/api/compute/images/test_image_metadata.py
index ad211ce..91eb4c5 100644
--- a/tempest/api/compute/images/test_image_metadata.py
+++ b/tempest/api/compute/images/test_image_metadata.py
@@ -16,7 +16,7 @@
from tempest.api.compute import base
from tempest.common.utils import data_utils
from tempest import config
-from tempest.test import attr
+from tempest import test
CONF = config.CONF
@@ -24,14 +24,15 @@
class ImagesMetadataTestJSON(base.BaseV2ComputeTest):
@classmethod
+ @test.safe_setup
def setUpClass(cls):
super(ImagesMetadataTestJSON, cls).setUpClass()
if not CONF.service_available.glance:
skip_msg = ("%s skipped as glance is not available" % cls.__name__)
raise cls.skipException(skip_msg)
- cls.servers_client = cls.servers_client
cls.client = cls.images_client
+ cls.image_id = None
resp, server = cls.create_test_server(wait_until='ACTIVE')
cls.server_id = server['id']
@@ -45,7 +46,8 @@
@classmethod
def tearDownClass(cls):
- cls.client.delete_image(cls.image_id)
+ if cls.image_id:
+ cls.client.delete_image(cls.image_id)
super(ImagesMetadataTestJSON, cls).tearDownClass()
def setUp(self):
@@ -54,14 +56,14 @@
resp, _ = self.client.set_image_metadata(self.image_id, meta)
self.assertEqual(resp.status, 200)
- @attr(type='gate')
+ @test.attr(type='gate')
def test_list_image_metadata(self):
# All metadata key/value pairs for an image should be returned
resp, resp_metadata = self.client.list_image_metadata(self.image_id)
expected = {'key1': 'value1', 'key2': 'value2'}
self.assertEqual(expected, resp_metadata)
- @attr(type='gate')
+ @test.attr(type='gate')
def test_set_image_metadata(self):
# The metadata for the image should match the new values
req_metadata = {'meta2': 'value2', 'meta3': 'value3'}
@@ -71,7 +73,7 @@
resp, resp_metadata = self.client.list_image_metadata(self.image_id)
self.assertEqual(req_metadata, resp_metadata)
- @attr(type='gate')
+ @test.attr(type='gate')
def test_update_image_metadata(self):
# The metadata for the image should match the updated values
req_metadata = {'key1': 'alt1', 'key3': 'value3'}
@@ -82,14 +84,14 @@
expected = {'key1': 'alt1', 'key2': 'value2', 'key3': 'value3'}
self.assertEqual(expected, resp_metadata)
- @attr(type='gate')
+ @test.attr(type='gate')
def test_get_image_metadata_item(self):
# The value for a specific metadata key should be returned
resp, meta = self.client.get_image_metadata_item(self.image_id,
'key2')
self.assertEqual('value2', meta['key2'])
- @attr(type='gate')
+ @test.attr(type='gate')
def test_set_image_metadata_item(self):
# The value provided for the given meta item should be set for
# the image
@@ -100,7 +102,7 @@
expected = {'key1': 'alt', 'key2': 'value2'}
self.assertEqual(expected, resp_metadata)
- @attr(type='gate')
+ @test.attr(type='gate')
def test_delete_image_metadata_item(self):
# The metadata value/key pair should be deleted from the image
resp, body = self.client.delete_image_metadata_item(self.image_id,
diff --git a/tempest/api/compute/images/test_image_metadata_negative.py b/tempest/api/compute/images/test_image_metadata_negative.py
index 7776c57..15bb66a 100644
--- a/tempest/api/compute/images/test_image_metadata_negative.py
+++ b/tempest/api/compute/images/test_image_metadata_negative.py
@@ -16,7 +16,7 @@
from tempest.api.compute import base
from tempest.common.utils import data_utils
from tempest import exceptions
-from tempest.test import attr
+from tempest import test
class ImagesMetadataTestJSON(base.BaseV2ComputeTest):
@@ -26,14 +26,14 @@
super(ImagesMetadataTestJSON, cls).setUpClass()
cls.client = cls.images_client
- @attr(type=['negative', 'gate'])
+ @test.attr(type=['negative', 'gate'])
def test_list_nonexistent_image_metadata(self):
# Negative test: List on nonexistent image
# metadata should not happen
self.assertRaises(exceptions.NotFound, self.client.list_image_metadata,
data_utils.rand_uuid())
- @attr(type=['negative', 'gate'])
+ @test.attr(type=['negative', 'gate'])
def test_update_nonexistent_image_metadata(self):
# Negative test:An update should not happen for a non-existent image
meta = {'key1': 'alt1', 'key2': 'alt2'}
@@ -41,21 +41,21 @@
self.client.update_image_metadata,
data_utils.rand_uuid(), meta)
- @attr(type=['negative', 'gate'])
+ @test.attr(type=['negative', 'gate'])
def test_get_nonexistent_image_metadata_item(self):
# Negative test: Get on non-existent image should not happen
self.assertRaises(exceptions.NotFound,
self.client.get_image_metadata_item,
data_utils.rand_uuid(), 'key2')
- @attr(type=['negative', 'gate'])
+ @test.attr(type=['negative', 'gate'])
def test_set_nonexistent_image_metadata(self):
# Negative test: Metadata should not be set to a non-existent image
meta = {'key1': 'alt1', 'key2': 'alt2'}
self.assertRaises(exceptions.NotFound, self.client.set_image_metadata,
data_utils.rand_uuid(), meta)
- @attr(type=['negative', 'gate'])
+ @test.attr(type=['negative', 'gate'])
def test_set_nonexistent_image_metadata_item(self):
# Negative test: Metadata item should not be set to a
# nonexistent image
@@ -65,7 +65,7 @@
data_utils.rand_uuid(), 'key1',
meta)
- @attr(type=['negative', 'gate'])
+ @test.attr(type=['negative', 'gate'])
def test_delete_nonexistent_image_metadata_item(self):
# Negative test: Shouldn't be able to delete metadata
# item from non-existent image
diff --git a/tempest/api/compute/images/test_images.py b/tempest/api/compute/images/test_images.py
index ed316ef..29df2b0 100644
--- a/tempest/api/compute/images/test_images.py
+++ b/tempest/api/compute/images/test_images.py
@@ -15,7 +15,6 @@
from tempest.api.compute import base
from tempest.common.utils import data_utils
from tempest import config
-from tempest import exceptions
from tempest import test
CONF = config.CONF
@@ -32,63 +31,6 @@
cls.client = cls.images_client
cls.servers_client = cls.servers_client
- cls.image_ids = []
-
- 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(ImagesTestJSON, self).tearDown()
-
- def __create_image__(self, server_id, name, meta=None):
- resp, body = self.client.create_image(server_id, name, meta)
- 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
-
- @test.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)
-
- @test.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)
-
- @test.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',
- wait_for_server=False)
- self.addCleanup(self.client.delete_image, image['id'])
- self.assertEqual(snapshot_name, image['name'])
-
@test.attr(type='gate')
def test_delete_saving_image(self):
snapshot_name = data_utils.rand_name('test-snap-')
@@ -100,59 +42,6 @@
resp, body = self.client.delete_image(image['id'])
self.assertEqual('204', resp['status'])
- @test.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.client.create_image,
- test_uuid, snapshot_name)
-
- @test.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.client.create_image,
- test_uuid, snapshot_name)
-
- @test.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,
- '!@$%^&*()')
-
- @test.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)
-
- @test.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, '')
-
- @test.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)
-
- @test.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)
-
- @test.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')
-
class ImagesTestXML(ImagesTestJSON):
_interface = 'xml'
diff --git a/tempest/api/compute/images/test_images_negative.py b/tempest/api/compute/images/test_images_negative.py
new file mode 100644
index 0000000..ae00ae2
--- /dev/null
+++ b/tempest/api/compute/images/test_images_negative.py
@@ -0,0 +1,131 @@
+# 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.compute import base
+from tempest.common.utils import data_utils
+from tempest import config
+from tempest import exceptions
+from tempest import test
+
+CONF = config.CONF
+
+
+class ImagesNegativeTestJSON(base.BaseV2ComputeTest):
+
+ @classmethod
+ def setUpClass(cls):
+ super(ImagesNegativeTestJSON, cls).setUpClass()
+ if not CONF.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
+
+ @test.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_from_server,
+ server['id'], name=name, meta=meta)
+
+ @test.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_from_server,
+ '!@#$%^&*()', name=name, meta=meta)
+
+ @test.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',
+ wait_for_server=False)
+ self.addCleanup(self.client.delete_image, image['id'])
+ self.assertEqual(snapshot_name, image['name'])
+
+ @test.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.client.create_image,
+ test_uuid, snapshot_name)
+
+ @test.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.client.create_image,
+ test_uuid, snapshot_name)
+
+ @test.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,
+ '!@$%^&*()')
+
+ @test.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)
+
+ @test.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, '')
+
+ @test.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)
+
+ @test.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)
+
+ @test.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')
+
+
+class ImagesNegativeTestXML(ImagesNegativeTestJSON):
+ _interface = 'xml'
diff --git a/tempest/api/compute/images/test_images_oneserver.py b/tempest/api/compute/images/test_images_oneserver.py
index b152c3c..d2fd970 100644
--- a/tempest/api/compute/images/test_images_oneserver.py
+++ b/tempest/api/compute/images/test_images_oneserver.py
@@ -13,7 +13,6 @@
# 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
@@ -61,8 +60,6 @@
resp, flavor = self.flavors_client.get_flavor_details(flavor_id)
return flavor['disk']
- @testtools.skipUnless(CONF.compute_feature_enabled.create_image,
- 'Environment unable to create images.')
@test.attr(type='smoke')
def test_create_delete_image(self):
diff --git a/tempest/api/compute/images/test_list_images.py b/tempest/api/compute/images/test_list_images.py
index 4074a7a..eba331f 100644
--- a/tempest/api/compute/images/test_list_images.py
+++ b/tempest/api/compute/images/test_list_images.py
@@ -15,7 +15,7 @@
from tempest.api.compute import base
from tempest import config
-from tempest.test import attr
+from tempest import test
CONF = config.CONF
@@ -30,20 +30,20 @@
raise cls.skipException(skip_msg)
cls.client = cls.images_client
- @attr(type='smoke')
+ @test.attr(type='smoke')
def test_get_image(self):
# Returns the correct details for a single image
resp, image = self.client.get_image(self.image_ref)
self.assertEqual(self.image_ref, image['id'])
- @attr(type='smoke')
+ @test.attr(type='smoke')
def test_list_images(self):
# The list of all images should contain the image
resp, images = self.client.list_images()
found = any([i for i in images if i['id'] == self.image_ref])
self.assertTrue(found)
- @attr(type='smoke')
+ @test.attr(type='smoke')
def test_list_images_with_detail(self):
# Detailed list of all images should contain the expected images
resp, images = self.client.list_images_with_detail()
diff --git a/tempest/api/compute/security_groups/test_security_groups.py b/tempest/api/compute/security_groups/test_security_groups.py
index c063a4e..538ebc6 100644
--- a/tempest/api/compute/security_groups/test_security_groups.py
+++ b/tempest/api/compute/security_groups/test_security_groups.py
@@ -26,7 +26,7 @@
super(SecurityGroupsTestJSON, cls).setUpClass()
cls.client = cls.security_groups_client
- @test.attr(type='gate')
+ @test.attr(type='smoke')
def test_security_groups_create_list_delete(self):
# Positive test:Should return the list of Security Groups
# Create 3 Security Groups
@@ -58,7 +58,7 @@
"list" % ', '.join(m_group['name']
for m_group in deleted_sgs))
- @test.attr(type='gate')
+ @test.attr(type='smoke')
def test_security_group_create_get_delete(self):
# Security Group should be created, fetched and deleted
s_name = data_utils.rand_name('securitygroup-')
@@ -76,7 +76,7 @@
"The fetched Security Group is different "
"from the created Group")
- @test.attr(type='gate')
+ @test.attr(type='smoke')
def test_server_security_groups(self):
# Checks that security groups may be added and linked to a server
# and not deleted if the server is active.
@@ -122,7 +122,7 @@
self.client.delete_security_group(sg2['id'])
self.assertEqual(202, resp.status)
- @test.attr(type='gate')
+ @test.attr(type='smoke')
def test_update_security_groups(self):
# Update security group name and description
# Create a security group
diff --git a/tempest/api/compute/security_groups/test_security_groups_negative.py b/tempest/api/compute/security_groups/test_security_groups_negative.py
index 735c7ce..aa2d32e 100644
--- a/tempest/api/compute/security_groups/test_security_groups_negative.py
+++ b/tempest/api/compute/security_groups/test_security_groups_negative.py
@@ -56,7 +56,7 @@
@test.skip_because(bug="1161411",
condition=CONF.service_available.neutron)
- @test.attr(type=['negative', 'gate'])
+ @test.attr(type=['negative', 'smoke'])
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
@@ -76,7 +76,7 @@
@test.skip_because(bug="1161411",
condition=CONF.service_available.neutron)
- @test.attr(type=['negative', 'gate'])
+ @test.attr(type=['negative', 'smoke'])
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
@@ -95,7 +95,7 @@
@testtools.skipIf(CONF.service_available.neutron,
"Neutron allows duplicate names for security groups")
- @test.attr(type=['negative', 'gate'])
+ @test.attr(type=['negative', 'smoke'])
def test_security_group_create_with_duplicate_name(self):
# Negative test:Security Group with duplicate name should not
# be created
@@ -109,7 +109,7 @@
self.client.create_security_group, s_name,
s_description)
- @test.attr(type=['negative', 'gate'])
+ @test.attr(type=['negative', 'smoke'])
def test_delete_the_default_security_group(self):
# Negative test:Deletion of the "default" Security Group should Fail
default_security_group_id = None
@@ -130,7 +130,7 @@
self.assertRaises(exceptions.NotFound,
self.client.delete_security_group, non_exist_id)
- @test.attr(type=['negative', 'gate'])
+ @test.attr(type=['negative', 'smoke'])
def test_delete_security_group_without_passing_id(self):
# Negative test:Deletion of a Security Group with out passing ID
# should Fail
@@ -139,7 +139,7 @@
@testtools.skipIf(CONF.service_available.neutron,
"Neutron not check the security_group_id")
- @test.attr(type=['negative', 'gate'])
+ @test.attr(type=['negative', 'smoke'])
def test_update_security_group_with_invalid_sg_id(self):
# Update security_group with invalid sg_id should fail
s_name = data_utils.rand_name('sg-')
@@ -152,7 +152,7 @@
@testtools.skipIf(CONF.service_available.neutron,
"Neutron not check the security_group_name")
- @test.attr(type=['negative', 'gate'])
+ @test.attr(type=['negative', 'smoke'])
def test_update_security_group_with_invalid_sg_name(self):
# Update security_group with invalid sg_name should fail
resp, securitygroup = self.create_security_group()
@@ -167,7 +167,7 @@
@testtools.skipIf(CONF.service_available.neutron,
"Neutron not check the security_group_description")
- @test.attr(type=['negative', 'gate'])
+ @test.attr(type=['negative', 'smoke'])
def test_update_security_group_with_invalid_sg_des(self):
# Update security_group with invalid sg_des should fail
resp, securitygroup = self.create_security_group()
@@ -180,7 +180,7 @@
self.client.update_security_group,
securitygroup_id, description=s_new_des)
- @test.attr(type=['negative', 'gate'])
+ @test.attr(type=['negative', 'smoke'])
def test_update_non_existent_security_group(self):
# Update a non-existent Security Group should Fail
non_exist_id = self._generate_a_non_existent_security_group_id()
diff --git a/tempest/api/compute/servers/test_attach_interfaces.py b/tempest/api/compute/servers/test_attach_interfaces.py
index a21c411..f6eed00 100644
--- a/tempest/api/compute/servers/test_attach_interfaces.py
+++ b/tempest/api/compute/servers/test_attach_interfaces.py
@@ -16,7 +16,7 @@
from tempest.api.compute import base
from tempest import config
from tempest import exceptions
-from tempest.test import attr
+from tempest import test
import time
@@ -106,7 +106,7 @@
self.assertEqual(sorted(list1), sorted(list2))
- @attr(type='smoke')
+ @test.attr(type='smoke')
def test_create_list_show_delete_interfaces(self):
server, ifs = self._create_server_get_interfaces()
interface_count = len(ifs)
@@ -127,7 +127,7 @@
_ifs = self._test_delete_interface(server, ifs)
self.assertEqual(len(ifs) - 1, len(_ifs))
- @attr(type='gate')
+ @test.attr(type='gate')
def test_add_remove_fixed_ip(self):
# Add and Remove the fixed IP to server.
server, ifs = self._create_server_get_interfaces()
diff --git a/tempest/api/compute/servers/test_create_server.py b/tempest/api/compute/servers/test_create_server.py
index ddf37ce..778294e 100644
--- a/tempest/api/compute/servers/test_create_server.py
+++ b/tempest/api/compute/servers/test_create_server.py
@@ -186,8 +186,7 @@
admin_pass = self.image_ssh_password
- resp, server_no_eph_disk = (self.
- create_test_server(
+ resp, server_no_eph_disk = (self.create_test_server(
wait_until='ACTIVE',
adminPass=admin_pass,
flavor=flavor_no_eph_disk_id))
diff --git a/tempest/api/compute/servers/test_disk_config.py b/tempest/api/compute/servers/test_disk_config.py
index 75a1234..332358c 100644
--- a/tempest/api/compute/servers/test_disk_config.py
+++ b/tempest/api/compute/servers/test_disk_config.py
@@ -17,7 +17,7 @@
from tempest.api.compute import base
from tempest import config
-from tempest.test import attr
+from tempest import test
CONF = config.CONF
@@ -44,7 +44,7 @@
resp, server = self.client.get_server(server['id'])
self.assertEqual(disk_config, server['OS-DCF:diskConfig'])
- @attr(type='gate')
+ @test.attr(type='gate')
def test_rebuild_server_with_manual_disk_config(self):
# A server should be rebuilt using the manual disk config option
self._update_server_with_disk_config(disk_config='AUTO')
@@ -60,7 +60,7 @@
resp, server = self.client.get_server(server['id'])
self.assertEqual('MANUAL', server['OS-DCF:diskConfig'])
- @attr(type='gate')
+ @test.attr(type='gate')
def test_rebuild_server_with_auto_disk_config(self):
# A server should be rebuilt using the auto disk config option
self._update_server_with_disk_config(disk_config='MANUAL')
@@ -86,7 +86,7 @@
@testtools.skipUnless(CONF.compute_feature_enabled.resize,
'Resize not available.')
- @attr(type='gate')
+ @test.attr(type='gate')
def test_resize_server_from_manual_to_auto(self):
# A server should be resized from manual to auto disk config
self._update_server_with_disk_config(disk_config='MANUAL')
@@ -103,7 +103,7 @@
@testtools.skipUnless(CONF.compute_feature_enabled.resize,
'Resize not available.')
- @attr(type='gate')
+ @test.attr(type='gate')
def test_resize_server_from_auto_to_manual(self):
# A server should be resized from auto to manual disk config
self._update_server_with_disk_config(disk_config='AUTO')
@@ -118,7 +118,7 @@
resp, server = self.client.get_server(self.server_id)
self.assertEqual('MANUAL', server['OS-DCF:diskConfig'])
- @attr(type='gate')
+ @test.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')
diff --git a/tempest/api/compute/servers/test_list_server_filters.py b/tempest/api/compute/servers/test_list_server_filters.py
index 837114c..f0913f1 100644
--- a/tempest/api/compute/servers/test_list_server_filters.py
+++ b/tempest/api/compute/servers/test_list_server_filters.py
@@ -26,6 +26,7 @@
class ListServerFiltersTestJSON(base.BaseV2ComputeTest):
@classmethod
+ @test.safe_setup
def setUpClass(cls):
super(ListServerFiltersTestJSON, cls).setUpClass()
cls.client = cls.servers_client
diff --git a/tempest/api/compute/servers/test_list_servers_negative.py b/tempest/api/compute/servers/test_list_servers_negative.py
index 26c5887..768cc11 100644
--- a/tempest/api/compute/servers/test_list_servers_negative.py
+++ b/tempest/api/compute/servers/test_list_servers_negative.py
@@ -19,13 +19,14 @@
from tempest.api.compute import base
from tempest import exceptions
-from tempest.test import attr
+from tempest import test
class ListServersNegativeTestJSON(base.BaseV2ComputeTest):
force_tenant_isolation = True
@classmethod
+ @test.safe_setup
def setUpClass(cls):
super(ListServersNegativeTestJSON, cls).setUpClass()
cls.client = cls.servers_client
@@ -51,7 +52,7 @@
ignore_error=True)
cls.deleted_fixtures.append(srv)
- @attr(type=['negative', 'gate'])
+ @test.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
@@ -63,7 +64,7 @@
self.assertEqual('200', resp['status'])
self.assertEqual([], actual)
- @attr(type=['negative', 'gate'])
+ @test.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'
@@ -72,7 +73,7 @@
self.assertEqual('200', resp['status'])
self.assertEqual([], servers)
- @attr(type=['negative', 'gate'])
+ @test.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
@@ -81,7 +82,7 @@
self.assertEqual('200', resp['status'])
self.assertEqual([], servers)
- @attr(type=['negative', 'gate'])
+ @test.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'
@@ -90,7 +91,7 @@
self.assertEqual('200', resp['status'])
self.assertEqual([], servers)
- @attr(type=['negative', 'gate'])
+ @test.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'
@@ -99,7 +100,7 @@
self.assertEqual('200', resp['status'])
self.assertEqual([], servers)
- @attr(type='gate')
+ @test.attr(type='gate')
def test_list_servers_by_limits(self):
# List servers by specifying limits
resp, body = self.client.list_servers({'limit': 1})
@@ -107,26 +108,26 @@
# 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'])
+ @test.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'])
+ @test.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'])
+ @test.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')
+ @test.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()}
@@ -139,13 +140,13 @@
"Number of servers %d is wrong in %s" %
(num_expected, body['servers']))
- @attr(type=['negative', 'gate'])
+ @test.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'])
+ @test.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'}
@@ -153,7 +154,7 @@
self.assertEqual('200', resp['status'])
self.assertEqual(0, len(body['servers']))
- @attr(type=['negative', 'gate'])
+ @test.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]
diff --git a/tempest/api/compute/servers/test_server_actions.py b/tempest/api/compute/servers/test_server_actions.py
index 8afd5f9..b8be5f3 100644
--- a/tempest/api/compute/servers/test_server_actions.py
+++ b/tempest/api/compute/servers/test_server_actions.py
@@ -17,6 +17,7 @@
import time
import testtools
+import urlparse
from tempest.api.compute import base
from tempest.common.utils import data_utils
@@ -384,6 +385,11 @@
self.client.wait_for_server_status(self.server_id,
'SHELVED')
+ resp, server = self.client.shelve_offload_server(self.server_id)
+ self.assertEqual(202, resp.status)
+ self.client.wait_for_server_status(self.server_id,
+ 'SHELVED_OFFLOADED')
+
resp, server = self.client.get_server(self.server_id)
image_name = server['name'] + '-shelved'
params = {'name': image_name}
@@ -424,6 +430,29 @@
self.assertEqual(202, resp.status)
self.servers_client.wait_for_server_status(self.server_id, 'ACTIVE')
+ def _validate_url(self, url):
+ valid_scheme = ['http', 'https']
+ parsed_url = urlparse.urlparse(url)
+ self.assertNotEqual('None', parsed_url.port)
+ self.assertNotEqual('None', parsed_url.hostname)
+ self.assertIn(parsed_url.scheme, valid_scheme)
+
+ @testtools.skipUnless(CONF.compute_feature_enabled.vnc_console,
+ 'VNC Console feature is disabled.')
+ @test.attr(type='gate')
+ def test_get_vnc_console(self):
+ # Get the VNC console of type 'novnc' and 'xvpvnc'
+ console_types = ['novnc', 'xvpvnc']
+ for console_type in console_types:
+ resp, body = self.servers_client.get_vnc_console(self.server_id,
+ console_type)
+ self.assertEqual(
+ 200, resp.status,
+ "Failed to get Console Type: %s" % (console_types))
+ self.assertEqual(console_type, body['type'])
+ self.assertNotEqual('', body['url'])
+ self._validate_url(body['url'])
+
class ServerActionsTestXML(ServerActionsTestJSON):
_interface = 'xml'
diff --git a/tempest/api/compute/servers/test_server_password.py b/tempest/api/compute/servers/test_server_password.py
index ad78f65..50c881a 100644
--- a/tempest/api/compute/servers/test_server_password.py
+++ b/tempest/api/compute/servers/test_server_password.py
@@ -15,7 +15,7 @@
from tempest.api.compute import base
-from tempest.test import attr
+from tempest import test
class ServerPasswordTestJSON(base.BaseV2ComputeTest):
@@ -26,12 +26,12 @@
cls.client = cls.servers_client
resp, cls.server = cls.create_test_server(wait_until="ACTIVE")
- @attr(type='gate')
+ @test.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')
+ @test.attr(type='gate')
def test_delete_server_password(self):
resp, body = self.client.delete_password(self.server['id'])
self.assertEqual(204, resp.status)
diff --git a/tempest/api/compute/servers/test_server_personality.py b/tempest/api/compute/servers/test_server_personality.py
index b581faf..b7e4e38 100644
--- a/tempest/api/compute/servers/test_server_personality.py
+++ b/tempest/api/compute/servers/test_server_personality.py
@@ -17,7 +17,7 @@
from tempest.api.compute import base
from tempest import exceptions
-from tempest.test import attr
+from tempest import test
class ServerPersonalityTestJSON(base.BaseV2ComputeTest):
@@ -28,7 +28,7 @@
cls.client = cls.servers_client
cls.user_client = cls.limits_client
- @attr(type='gate')
+ @test.attr(type='gate')
def test_personality_files_exceed_limit(self):
# Server creation should fail if greater than the maximum allowed
# number of files are injected into the server.
@@ -43,7 +43,7 @@
self.assertRaises(exceptions.OverLimit, self.create_test_server,
personality=personality)
- @attr(type='gate')
+ @test.attr(type='gate')
def test_can_create_server_with_max_number_personality_files(self):
# Server should be created successfully if maximum allowed number of
# files is injected into the server during creation.
diff --git a/tempest/api/compute/servers/test_server_rescue.py b/tempest/api/compute/servers/test_server_rescue.py
index 48f2e14..093e9e2 100644
--- a/tempest/api/compute/servers/test_server_rescue.py
+++ b/tempest/api/compute/servers/test_server_rescue.py
@@ -21,6 +21,7 @@
class ServerRescueTestJSON(base.BaseV2ComputeTest):
@classmethod
+ @test.safe_setup
def setUpClass(cls):
cls.set_network_resources(network=True, subnet=True, router=True)
super(ServerRescueTestJSON, cls).setUpClass()
diff --git a/tempest/api/compute/servers/test_server_rescue_negative.py b/tempest/api/compute/servers/test_server_rescue_negative.py
index 277f28f..ef45585 100644
--- a/tempest/api/compute/servers/test_server_rescue_negative.py
+++ b/tempest/api/compute/servers/test_server_rescue_negative.py
@@ -22,6 +22,7 @@
class ServerRescueNegativeTestJSON(base.BaseV2ComputeTest):
@classmethod
+ @test.safe_setup
def setUpClass(cls):
cls.set_network_resources(network=True, subnet=True, router=True)
super(ServerRescueNegativeTestJSON, cls).setUpClass()
@@ -45,6 +46,11 @@
cls.rescue_id, adminPass=rescue_password)
cls.servers_client.wait_for_server_status(cls.rescue_id, 'RESCUE')
+ @classmethod
+ def tearDownClass(cls):
+ cls.delete_volume(cls.volume['id'])
+ super(ServerRescueNegativeTestJSON, cls).tearDownClass()
+
def _detach(self, server_id, volume_id):
self.servers_client.detach_volume(server_id, volume_id)
self.volumes_extensions_client.wait_for_volume_status(volume_id,
diff --git a/tempest/api/compute/servers/test_servers.py b/tempest/api/compute/servers/test_servers.py
index 9674463..40b97d7 100644
--- a/tempest/api/compute/servers/test_servers.py
+++ b/tempest/api/compute/servers/test_servers.py
@@ -15,7 +15,7 @@
from tempest.api.compute import base
from tempest.common.utils import data_utils
-from tempest.test import attr
+from tempest import test
class ServersTestJSON(base.BaseV2ComputeTest):
@@ -29,7 +29,7 @@
self.clear_servers()
super(ServersTestJSON, self).tearDown()
- @attr(type='gate')
+ @test.attr(type='gate')
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.
@@ -38,7 +38,7 @@
# Verify the password is set correctly in the response
self.assertEqual('testpassword', server['adminPass'])
- @attr(type='gate')
+ @test.attr(type='gate')
def test_create_with_existing_server_name(self):
# Creating a server with a name that already exists is allowed
@@ -57,7 +57,7 @@
name2 = server['name']
self.assertEqual(name1, name2)
- @attr(type='gate')
+ @test.attr(type='gate')
def test_create_specify_keypair(self):
# Specify a keypair while creating a server
@@ -70,7 +70,7 @@
resp, server = self.client.get_server(server['id'])
self.assertEqual(key_name, server['key_name'])
- @attr(type='gate')
+ @test.attr(type='gate')
def test_update_server_name(self):
# The server name should be changed to the the provided value
resp, server = self.create_test_server(wait_until='ACTIVE')
@@ -85,7 +85,7 @@
resp, server = self.client.get_server(server['id'])
self.assertEqual('newname', server['name'])
- @attr(type='gate')
+ @test.attr(type='gate')
def test_update_access_server_address(self):
# The server's access addresses should reflect the provided values
resp, server = self.create_test_server(wait_until='ACTIVE')
@@ -102,7 +102,7 @@
self.assertEqual('1.1.1.1', server['accessIPv4'])
self.assertEqual('::babe:202:202', server['accessIPv6'])
- @attr(type='gate')
+ @test.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')
diff --git a/tempest/api/compute/test_authorization.py b/tempest/api/compute/test_authorization.py
index fd73cc5..7f909d7 100644
--- a/tempest/api/compute/test_authorization.py
+++ b/tempest/api/compute/test_authorization.py
@@ -19,7 +19,7 @@
from tempest import config
from tempest import exceptions
from tempest.openstack.common import log as logging
-from tempest.test import attr
+from tempest import test
CONF = config.CONF
@@ -89,31 +89,31 @@
cls.security_client.delete_security_group(cls.security_group['id'])
super(AuthorizationTestJSON, cls).tearDownClass()
- @attr(type='gate')
+ @test.attr(type='gate')
def test_get_server_for_alt_account_fails(self):
# A GET request for a server on another user's account should fail
self.assertRaises(exceptions.NotFound, self.alt_client.get_server,
self.server['id'])
- @attr(type='gate')
+ @test.attr(type='gate')
def test_delete_server_for_alt_account_fails(self):
# A DELETE request for another user's server should fail
self.assertRaises(exceptions.NotFound, self.alt_client.delete_server,
self.server['id'])
- @attr(type='gate')
+ @test.attr(type='gate')
def test_update_server_for_alt_account_fails(self):
# An update server request for another user's server should fail
self.assertRaises(exceptions.NotFound, self.alt_client.update_server,
self.server['id'], name='test')
- @attr(type='gate')
+ @test.attr(type='gate')
def test_list_server_addresses_for_alt_account_fails(self):
# A list addresses request for another user's server should fail
self.assertRaises(exceptions.NotFound, self.alt_client.list_addresses,
self.server['id'])
- @attr(type='gate')
+ @test.attr(type='gate')
def test_list_server_addresses_by_network_for_alt_account_fails(self):
# A list address/network request for another user's server should fail
server_id = self.server['id']
@@ -121,7 +121,7 @@
self.alt_client.list_addresses_by_network, server_id,
'public')
- @attr(type='gate')
+ @test.attr(type='gate')
def test_list_servers_with_alternate_tenant(self):
# A list on servers from one tenant should not
# show on alternate tenant
@@ -131,44 +131,44 @@
alt_server_ids = [s['id'] for s in body['servers']]
self.assertNotIn(self.server['id'], alt_server_ids)
- @attr(type='gate')
+ @test.attr(type='gate')
def test_change_password_for_alt_account_fails(self):
# A change password request for another user's server should fail
self.assertRaises(exceptions.NotFound, self.alt_client.change_password,
self.server['id'], 'newpass')
- @attr(type='gate')
+ @test.attr(type='gate')
def test_reboot_server_for_alt_account_fails(self):
# A reboot request for another user's server should fail
self.assertRaises(exceptions.NotFound, self.alt_client.reboot,
self.server['id'], 'HARD')
- @attr(type='gate')
+ @test.attr(type='gate')
def test_rebuild_server_for_alt_account_fails(self):
# A rebuild request for another user's server should fail
self.assertRaises(exceptions.NotFound, self.alt_client.rebuild,
self.server['id'], self.image_ref_alt)
- @attr(type='gate')
+ @test.attr(type='gate')
def test_resize_server_for_alt_account_fails(self):
# A resize request for another user's server should fail
self.assertRaises(exceptions.NotFound, self.alt_client.resize,
self.server['id'], self.flavor_ref_alt)
- @attr(type='gate')
+ @test.attr(type='gate')
def test_create_image_for_alt_account_fails(self):
# A create image request for another user's server should fail
self.assertRaises(exceptions.NotFound,
self.alt_images_client.create_image,
self.server['id'], 'testImage')
- @attr(type='gate')
+ @test.attr(type='gate')
def test_create_server_with_unauthorized_image(self):
# Server creation with another user's image should fail
self.assertRaises(exceptions.BadRequest, self.alt_client.create_server,
'test', self.image['id'], self.flavor_ref)
- @attr(type='gate')
+ @test.attr(type='gate')
def test_create_server_fails_when_tenant_incorrect(self):
# A create server request should fail if the tenant id does not match
# the current user
@@ -181,7 +181,7 @@
self.alt_client.create_server, 'test',
self.image['id'], self.flavor_ref)
- @attr(type='gate')
+ @test.attr(type='gate')
def test_create_keypair_in_analt_user_tenant(self):
# A create keypair request should fail if the tenant id does not match
# the current user
@@ -204,34 +204,34 @@
LOG.error("Create keypair request should not happen "
"if the tenant id does not match the current user")
- @attr(type='gate')
+ @test.attr(type='gate')
def test_get_keypair_of_alt_account_fails(self):
# A GET request for another user's keypair should fail
self.assertRaises(exceptions.NotFound,
self.alt_keypairs_client.get_keypair,
self.keypairname)
- @attr(type='gate')
+ @test.attr(type='gate')
def test_delete_keypair_of_alt_account_fails(self):
# A DELETE request for another user's keypair should fail
self.assertRaises(exceptions.NotFound,
self.alt_keypairs_client.delete_keypair,
self.keypairname)
- @attr(type='gate')
+ @test.attr(type='gate')
def test_get_image_for_alt_account_fails(self):
# A GET request for an image on another user's account should fail
self.assertRaises(exceptions.NotFound,
self.alt_images_client.get_image, self.image['id'])
- @attr(type='gate')
+ @test.attr(type='gate')
def test_delete_image_for_alt_account_fails(self):
# A DELETE request for another user's image should fail
self.assertRaises(exceptions.NotFound,
self.alt_images_client.delete_image,
self.image['id'])
- @attr(type='gate')
+ @test.attr(type='gate')
def test_create_security_group_in_analt_user_tenant(self):
# A create security group request should fail if the tenant id does not
# match the current user
@@ -256,21 +256,21 @@
LOG.error("Create Security Group request should not happen if"
"the tenant id does not match the current user")
- @attr(type='gate')
+ @test.attr(type='gate')
def test_get_security_group_of_alt_account_fails(self):
# A GET request for another user's security group should fail
self.assertRaises(exceptions.NotFound,
self.alt_security_client.get_security_group,
self.security_group['id'])
- @attr(type='gate')
+ @test.attr(type='gate')
def test_delete_security_group_of_alt_account_fails(self):
# A DELETE request for another user's security group should fail
self.assertRaises(exceptions.NotFound,
self.alt_security_client.delete_security_group,
self.security_group['id'])
- @attr(type='gate')
+ @test.attr(type='gate')
def test_create_security_group_rule_in_analt_user_tenant(self):
# A create security group rule request should fail if the tenant id
# does not match the current user
@@ -300,7 +300,7 @@
"happen if the tenant id does not match the"
" current user")
- @attr(type='gate')
+ @test.attr(type='gate')
def test_delete_security_group_rule_of_alt_account_fails(self):
# A DELETE request for another user's security group rule
# should fail
@@ -308,7 +308,7 @@
self.alt_security_client.delete_security_group_rule,
self.rule['id'])
- @attr(type='gate')
+ @test.attr(type='gate')
def test_set_metadata_of_alt_account_server_fails(self):
# A set metadata for another user's server should fail
req_metadata = {'meta1': 'data1', 'meta2': 'data2'}
@@ -317,7 +317,7 @@
self.server['id'],
req_metadata)
- @attr(type='gate')
+ @test.attr(type='gate')
def test_set_metadata_of_alt_account_image_fails(self):
# A set metadata for another user's image should fail
req_metadata = {'meta1': 'value1', 'meta2': 'value2'}
@@ -325,7 +325,7 @@
self.alt_images_client.set_image_metadata,
self.image['id'], req_metadata)
- @attr(type='gate')
+ @test.attr(type='gate')
def test_get_metadata_of_alt_account_server_fails(self):
# A get metadata for another user's server should fail
req_metadata = {'meta1': 'data1'}
@@ -336,7 +336,7 @@
self.alt_client.get_server_metadata_item,
self.server['id'], 'meta1')
- @attr(type='gate')
+ @test.attr(type='gate')
def test_get_metadata_of_alt_account_image_fails(self):
# A get metadata for another user's image should fail
req_metadata = {'meta1': 'value1'}
@@ -348,7 +348,7 @@
self.alt_images_client.get_image_metadata_item,
self.image['id'], 'meta1')
- @attr(type='gate')
+ @test.attr(type='gate')
def test_delete_metadata_of_alt_account_server_fails(self):
# A delete metadata for another user's server should fail
req_metadata = {'meta1': 'data1'}
@@ -359,7 +359,7 @@
self.alt_client.delete_server_metadata_item,
self.server['id'], 'meta1')
- @attr(type='gate')
+ @test.attr(type='gate')
def test_delete_metadata_of_alt_account_image_fails(self):
# A delete metadata for another user's image should fail
req_metadata = {'meta1': 'data1'}
@@ -371,7 +371,7 @@
self.alt_images_client.delete_image_metadata_item,
self.image['id'], 'meta1')
- @attr(type='gate')
+ @test.attr(type='gate')
def test_get_console_output_of_alt_account_server_fails(self):
# A Get Console Output for another user's server should fail
self.assertRaises(exceptions.NotFound,
diff --git a/tempest/api/compute/test_live_block_migration.py b/tempest/api/compute/test_live_block_migration.py
index 1319f68..93ff4b0 100644
--- a/tempest/api/compute/test_live_block_migration.py
+++ b/tempest/api/compute/test_live_block_migration.py
@@ -18,7 +18,7 @@
from tempest.api.compute import base
from tempest import config
-from tempest.test import attr
+from tempest import test
CONF = config.CONF
@@ -85,7 +85,7 @@
@testtools.skipIf(not CONF.compute_feature_enabled.live_migration,
'Live migration not available')
- @attr(type='gate')
+ @test.attr(type='gate')
def test_live_block_migration(self):
# Live block migrate an instance to another host
if len(self._get_compute_hostnames()) < 2:
@@ -105,7 +105,7 @@
@testtools.skipIf(not CONF.compute_feature_enabled.
block_migrate_cinder_iscsi,
'Block Live migration not configured for iSCSI')
- @attr(type='gate')
+ @test.attr(type='gate')
def test_iscsi_volume(self):
# Live block migrate an instance to another host
if len(self._get_compute_hostnames()) < 2:
diff --git a/tempest/api/compute/test_quotas.py b/tempest/api/compute/test_quotas.py
index b97ab2c..4db8c56 100644
--- a/tempest/api/compute/test_quotas.py
+++ b/tempest/api/compute/test_quotas.py
@@ -14,7 +14,7 @@
# under the License.
from tempest.api.compute import base
-from tempest.test import attr
+from tempest import test
class QuotasTestJSON(base.BaseV2ComputeTest):
@@ -27,6 +27,9 @@
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, users = cls.admin_client.list_users_for_tenant(cls.tenant_id)
+ cls.user_id = [user['id'] for user in users if user['name'] ==
+ cls.client.user][0]
cls.default_quota_set = set(('injected_file_content_bytes',
'metadata_items', 'injected_files',
'ram', 'floating_ips',
@@ -35,7 +38,7 @@
'instances', 'security_group_rules',
'cores', 'security_groups'))
- @attr(type='smoke')
+ @test.attr(type='smoke')
def test_get_quotas(self):
# User can get the quota set for it's tenant
expected_quota_set = self.default_quota_set | set(['id'])
@@ -45,7 +48,15 @@
sorted(quota_set.keys()))
self.assertEqual(quota_set['id'], self.tenant_id)
- @attr(type='smoke')
+ # get the quota set using user id
+ resp, quota_set = self.client.get_quota_set(self.tenant_id,
+ self.user_id)
+ self.assertEqual(200, resp.status)
+ self.assertEqual(sorted(expected_quota_set),
+ sorted(quota_set.keys()))
+ self.assertEqual(quota_set['id'], self.tenant_id)
+
+ @test.attr(type='smoke')
def test_get_default_quotas(self):
# User can get the default quota set for it's tenant
expected_quota_set = self.default_quota_set | set(['id'])
@@ -55,7 +66,7 @@
sorted(quota_set.keys()))
self.assertEqual(quota_set['id'], self.tenant_id)
- @attr(type='smoke')
+ @test.attr(type='smoke')
def test_compare_tenant_quotas_with_default_quotas(self):
# Tenants are created with the default quota values
resp, defualt_quota_set = \
diff --git a/tempest/api/compute/v3/admin/test_agents.py b/tempest/api/compute/v3/admin/test_agents.py
new file mode 100644
index 0000000..9d01b71
--- /dev/null
+++ b/tempest/api/compute/v3/admin/test_agents.py
@@ -0,0 +1,91 @@
+# Copyright 2014 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 test
+
+
+class AgentsAdminV3Test(base.BaseV3ComputeAdminTest):
+
+ """
+ Tests Agents API that require admin privileges
+ """
+
+ @classmethod
+ def setUpClass(cls):
+ super(AgentsAdminV3Test, cls).setUpClass()
+ cls.client = cls.agents_admin_client
+
+ @test.attr(type='gate')
+ def test_create_update_list_delete_agents(self):
+
+ """
+ 1. Create 2 agents.
+ 2. Update one of the agents.
+ 3. List all agent builds.
+ 4. List the agent builds by the filter.
+ 5. Delete agents.
+ """
+ params_kvm = expected_kvm = {'hypervisor': 'kvm',
+ 'os': 'win',
+ 'architecture': 'x86',
+ 'version': '7.0',
+ 'url': 'xxx://xxxx/xxx/xxx',
+ 'md5hash': ("""add6bb58e139be103324d04d"""
+ """82d8f545""")}
+
+ resp, agent_kvm = self.client.create_agent(**params_kvm)
+ self.assertEqual(201, resp.status)
+ for expected_item, value in expected_kvm.items():
+ self.assertEqual(value, agent_kvm[expected_item])
+
+ params_xen = expected_xen = {'hypervisor': 'xen',
+ 'os': 'linux',
+ 'architecture': 'x86',
+ 'version': '7.0',
+ 'url': 'xxx://xxxx/xxx/xxx1',
+ 'md5hash': """add6bb58e139be103324d04d8"""
+ """2d8f546"""}
+
+ resp, agent_xen = self.client.create_agent(**params_xen)
+ self.assertEqual(201, resp.status)
+
+ for expected_item, value in expected_xen.items():
+ self.assertEqual(value, agent_xen[expected_item])
+
+ params_kvm_new = expected_kvm_new = {'version': '8.0',
+ 'url': 'xxx://xxxx/xxx/xxx2',
+ 'md5hash': """add6bb58e139be103"""
+ """324d04d82d8f547"""}
+
+ resp, resp_agent_kvm = self.client.update_agent(agent_kvm['agent_id'],
+ **params_kvm_new)
+ self.assertEqual(200, resp.status)
+ for expected_item, value in expected_kvm_new.items():
+ self.assertEqual(value, resp_agent_kvm[expected_item])
+
+ resp, agents = self.client.list_agents()
+ self.assertEqual(200, resp.status)
+ self.assertTrue(len(agents) > 1)
+
+ params_filter = {'hypervisor': 'kvm'}
+ resp, agent = self.client.list_agents(params_filter)
+ self.assertEqual(200, resp.status)
+ self.assertTrue(len(agent) > 0)
+ self.assertEqual('kvm', agent[0]['hypervisor'])
+
+ resp, _ = self.client.delete_agent(agent_kvm['agent_id'])
+ self.assertEqual(204, resp.status)
+ resp, _ = self.client.delete_agent(agent_xen['agent_id'])
+ self.assertEqual(204, resp.status)
diff --git a/tempest/api/compute/v3/admin/test_quotas.py b/tempest/api/compute/v3/admin/test_quotas.py
index 0c138bb..917c115 100644
--- a/tempest/api/compute/v3/admin/test_quotas.py
+++ b/tempest/api/compute/v3/admin/test_quotas.py
@@ -56,7 +56,7 @@
def test_get_quota_set_detail(self):
# Admin can get the detail of resource quota set for a tenant
expected_quota_set = self.default_quota_set | set(['id'])
- expected_detail = {'reserved', 'limit', 'in_use'}
+ expected_detail = ['reserved', 'limit', 'in_use']
resp, quota_set = self.adm_client.get_quota_set_detail(
self.demo_tenant_id)
self.assertEqual(200, resp.status)
@@ -109,3 +109,26 @@
resp, quota_set = self.adm_client.get_quota_set(tenant_id)
self.assertEqual(200, resp.status)
self.assertEqual(quota_set['ram'], 5120)
+
+ @test.attr(type='gate')
+ def test_delete_quota(self):
+ # Admin can delete the resource quota set for a 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,
+ description=tenant_desc)
+ tenant_id = tenant['id']
+ self.addCleanup(identity_client.delete_tenant, tenant_id)
+ resp, quota_set_default = self.adm_client.get_quota_set(tenant_id)
+ self.assertEqual(200, resp.status)
+ ram_default = quota_set_default['ram']
+
+ self.adm_client.update_quota_set(tenant_id, ram='5120')
+ self.assertEqual(200, resp.status)
+ resp, _ = self.adm_client.delete_quota_set(tenant_id)
+ self.assertEqual(204, resp.status)
+
+ resp, quota_set_new = self.adm_client.get_quota_set(tenant_id)
+ self.assertEqual(200, resp.status)
+ self.assertEqual(ram_default, quota_set_new['ram'])
diff --git a/tempest/api/compute/v3/admin/test_servers.py b/tempest/api/compute/v3/admin/test_servers.py
index fb8afe4..579a535 100644
--- a/tempest/api/compute/v3/admin/test_servers.py
+++ b/tempest/api/compute/v3/admin/test_servers.py
@@ -27,6 +27,7 @@
_host_key = 'os-extended-server-attributes:host'
@classmethod
+ @test.safe_setup
def setUpClass(cls):
super(ServersAdminV3Test, cls).setUpClass()
cls.client = cls.servers_admin_client
diff --git a/tempest/api/compute/v3/admin/test_services.py b/tempest/api/compute/v3/admin/test_services.py
index 914a2a4..b367dad 100644
--- a/tempest/api/compute/v3/admin/test_services.py
+++ b/tempest/api/compute/v3/admin/test_services.py
@@ -48,10 +48,12 @@
@attr(type='gate')
def test_get_service_by_host_name(self):
resp, services = self.client.list_services()
+ self.assertEqual(200, resp.status)
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
@@ -69,6 +71,7 @@
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))
diff --git a/tempest/api/compute/v3/images/test_images_negative.py b/tempest/api/compute/v3/images/test_images_negative.py
index c38373f..0705bdc 100644
--- a/tempest/api/compute/v3/images/test_images_negative.py
+++ b/tempest/api/compute/v3/images/test_images_negative.py
@@ -35,7 +35,7 @@
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')
+ self.client.wait_for_image_status(image_id, 'active')
return resp, body
@test.attr(type=['negative', 'gate'])
diff --git a/tempest/api/compute/v3/images/test_images_oneserver.py b/tempest/api/compute/v3/images/test_images_oneserver.py
index 48a885e..3aab1e1 100644
--- a/tempest/api/compute/v3/images/test_images_oneserver.py
+++ b/tempest/api/compute/v3/images/test_images_oneserver.py
@@ -13,7 +13,6 @@
# 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
@@ -61,8 +60,6 @@
resp, flavor = self.flavors_client.get_flavor_details(flavor_id)
return flavor['disk']
- @testtools.skipUnless(CONF.compute_feature_enabled.create_image,
- 'Environment unable to create images.')
@test.attr(type='smoke')
def test_create_delete_image(self):
@@ -73,26 +70,26 @@
name, meta)
self.assertEqual(202, resp.status)
image_id = data_utils.parse_image_id(resp['location'])
- self.client.wait_for_image_status(image_id, 'ACTIVE')
+ self.client.wait_for_image_status(image_id, 'active')
# Verify the image was created correctly
- resp, image = self.client.get_image(image_id)
+ resp, image = self.client.get_image_meta(image_id)
self.assertEqual(name, image['name'])
- self.assertEqual('test', image['metadata']['image_type'])
+ self.assertEqual('test', image['properties']['image_type'])
- resp, original_image = self.client.get_image(self.image_ref)
+ resp, original_image = self.client.get_image_meta(self.image_ref)
# Verify minRAM is the same as the original image
- self.assertEqual(image['minRam'], original_image['minRam'])
+ self.assertEqual(image['min_ram'], original_image['min_ram'])
# Verify minDisk is the same as the original image or the flavor size
flavor_disk_size = self._get_default_flavor_disk_size(self.flavor_ref)
- self.assertIn(str(image['minDisk']),
- (str(original_image['minDisk']), str(flavor_disk_size)))
+ self.assertIn(str(image['min_disk']),
+ (str(original_image['min_disk']), str(flavor_disk_size)))
# Verify the image was deleted correctly
resp, body = self.client.delete_image(image_id)
- self.assertEqual('204', resp['status'])
+ self.assertEqual('200', resp['status'])
self.client.wait_for_resource_deletion(image_id)
@test.attr(type=['gate'])
diff --git a/tempest/api/compute/v3/servers/test_create_server.py b/tempest/api/compute/v3/servers/test_create_server.py
index a212ca5..14a4338 100644
--- a/tempest/api/compute/v3/servers/test_create_server.py
+++ b/tempest/api/compute/v3/servers/test_create_server.py
@@ -92,14 +92,6 @@
@testtools.skipIf(not run_ssh, 'Instance validation tests are disabled.')
@test.attr(type='gate')
- def test_can_log_into_created_server(self):
- # Check that the user can authenticate with the generated password
- linux_client = remote_client.RemoteClient(self.server,
- self.ssh_user, self.password)
- self.assertTrue(linux_client.can_authenticate())
-
- @testtools.skipIf(not run_ssh, 'Instance validation tests are disabled.')
- @test.attr(type='gate')
def test_verify_created_server_vcpus(self):
# Verify that the number of vcpus reported by the instance matches
# the amount stated by the flavor
@@ -195,8 +187,7 @@
admin_pass = self.image_ssh_password
- resp, server_no_eph_disk = (self.
- create_test_server(
+ resp, server_no_eph_disk = (self.create_test_server(
wait_until='ACTIVE',
adminPass=admin_pass,
flavor=flavor_no_eph_disk_id))
diff --git a/tempest/api/compute/v3/servers/test_list_server_filters.py b/tempest/api/compute/v3/servers/test_list_server_filters.py
index ec31e8e..2cb176c 100644
--- a/tempest/api/compute/v3/servers/test_list_server_filters.py
+++ b/tempest/api/compute/v3/servers/test_list_server_filters.py
@@ -26,6 +26,7 @@
class ListServerFiltersV3Test(base.BaseV3ComputeTest):
@classmethod
+ @test.safe_setup
def setUpClass(cls):
super(ListServerFiltersV3Test, cls).setUpClass()
cls.client = cls.servers_client
diff --git a/tempest/api/compute/v3/servers/test_list_servers_negative.py b/tempest/api/compute/v3/servers/test_list_servers_negative.py
index 92f44fe..9cbc4e0 100644
--- a/tempest/api/compute/v3/servers/test_list_servers_negative.py
+++ b/tempest/api/compute/v3/servers/test_list_servers_negative.py
@@ -19,13 +19,14 @@
from tempest.api.compute import base
from tempest import exceptions
-from tempest.test import attr
+from tempest import test
class ListServersNegativeV3Test(base.BaseV3ComputeTest):
force_tenant_isolation = True
@classmethod
+ @test.safe_setup
def setUpClass(cls):
super(ListServersNegativeV3Test, cls).setUpClass()
cls.client = cls.servers_client
@@ -51,7 +52,7 @@
ignore_error=True)
cls.deleted_fixtures.append(srv)
- @attr(type=['negative', 'gate'])
+ @test.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
@@ -63,7 +64,7 @@
self.assertEqual('200', resp['status'])
self.assertEqual([], actual)
- @attr(type=['negative', 'gate'])
+ @test.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'
@@ -72,7 +73,7 @@
self.assertEqual('200', resp['status'])
self.assertEqual([], servers)
- @attr(type=['negative', 'gate'])
+ @test.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
@@ -81,7 +82,7 @@
self.assertEqual('200', resp['status'])
self.assertEqual([], servers)
- @attr(type=['negative', 'gate'])
+ @test.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'
@@ -90,7 +91,7 @@
self.assertEqual('200', resp['status'])
self.assertEqual([], servers)
- @attr(type=['negative', 'gate'])
+ @test.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'
@@ -99,33 +100,33 @@
self.assertEqual('200', resp['status'])
self.assertEqual([], servers)
- @attr(type='gate')
+ @test.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'])
self.assertEqual(1, len([x for x in body['servers'] if 'id' in x]))
- @attr(type=['negative', 'gate'])
+ @test.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'])
+ @test.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'])
+ @test.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')
+ @test.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()}
@@ -138,13 +139,13 @@
"Number of servers %d is wrong in %s" %
(num_expected, body['servers']))
- @attr(type=['negative', 'gate'])
+ @test.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'])
+ @test.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'}
@@ -152,7 +153,7 @@
self.assertEqual('200', resp['status'])
self.assertEqual(0, len(body['servers']))
- @attr(type=['negative', 'gate'])
+ @test.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]
diff --git a/tempest/api/compute/v3/servers/test_server_actions.py b/tempest/api/compute/v3/servers/test_server_actions.py
index 406c45a..555d028 100644
--- a/tempest/api/compute/v3/servers/test_server_actions.py
+++ b/tempest/api/compute/v3/servers/test_server_actions.py
@@ -372,6 +372,11 @@
self.client.wait_for_server_status(self.server_id,
'SHELVED')
+ resp, server = self.client.shelve_offload_server(self.server_id)
+ self.assertEqual(202, resp.status)
+ self.client.wait_for_server_status(self.server_id,
+ 'SHELVED_OFFLOADED')
+
resp, server = self.client.get_server(self.server_id)
image_name = server['name'] + '-shelved'
resp, images = self.images_client.image_list(name=image_name)
diff --git a/tempest/api/compute/v3/servers/test_server_rescue_negative.py b/tempest/api/compute/v3/servers/test_server_rescue_negative.py
index 6e09376..6bb441c 100644
--- a/tempest/api/compute/v3/servers/test_server_rescue_negative.py
+++ b/tempest/api/compute/v3/servers/test_server_rescue_negative.py
@@ -22,6 +22,7 @@
class ServerRescueNegativeV3Test(base.BaseV3ComputeTest):
@classmethod
+ @test.safe_setup
def setUpClass(cls):
super(ServerRescueNegativeV3Test, cls).setUpClass()
cls.device = 'vdf'
diff --git a/tempest/api/compute/v3/test_quotas.py b/tempest/api/compute/v3/test_quotas.py
index b53d9be..3fe62e9 100644
--- a/tempest/api/compute/v3/test_quotas.py
+++ b/tempest/api/compute/v3/test_quotas.py
@@ -27,6 +27,9 @@
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, users = cls.admin_client.list_users_for_tenant(cls.tenant_id)
+ cls.user_id = [user['id'] for user in users if user['name'] ==
+ cls.client.user][0]
cls.default_quota_set = set(('metadata_items',
'ram', 'floating_ips',
'fixed_ips', 'key_pairs',
@@ -43,6 +46,14 @@
sorted(quota_set.keys()))
self.assertEqual(quota_set['id'], self.tenant_id)
+ # get the quota set using user id
+ resp, quota_set = self.client.get_quota_set(self.tenant_id,
+ self.user_id)
+ self.assertEqual(200, resp.status)
+ self.assertEqual(sorted(expected_quota_set),
+ sorted(quota_set.keys()))
+ self.assertEqual(quota_set['id'], self.tenant_id)
+
@test.attr(type='smoke')
def test_get_default_quotas(self):
# User can get the default quota set for it's tenant
diff --git a/tempest/api/data_processing/base.py b/tempest/api/data_processing/base.py
index 5b272ef..73ad22b 100644
--- a/tempest/api/data_processing/base.py
+++ b/tempest/api/data_processing/base.py
@@ -27,8 +27,8 @@
def setUpClass(cls):
super(BaseDataProcessingTest, cls).setUpClass()
os = cls.get_client_manager()
- if not CONF.service_available.savanna:
- raise cls.skipException("Savanna support is required")
+ if not CONF.service_available.sahara:
+ raise cls.skipException("Sahara support is required")
cls.client = os.data_processing_client
# set some constants
diff --git a/tempest/api/data_processing/test_node_group_templates.py b/tempest/api/data_processing/test_node_group_templates.py
index ff4fa6a..a64c345 100644
--- a/tempest/api/data_processing/test_node_group_templates.py
+++ b/tempest/api/data_processing/test_node_group_templates.py
@@ -28,7 +28,7 @@
if template_name is None:
# generate random name if it's not specified
- template_name = data_utils.rand_name('savanna')
+ template_name = data_utils.rand_name('sahara')
# create simple node group template
resp, body, template_id = self.create_node_group_template(
diff --git a/tempest/api/identity/admin/test_roles.py b/tempest/api/identity/admin/test_roles.py
index 5e78cce..81bc5de 100644
--- a/tempest/api/identity/admin/test_roles.py
+++ b/tempest/api/identity/admin/test_roles.py
@@ -17,13 +17,14 @@
from tempest.api.identity import base
from tempest.common.utils import data_utils
-from tempest.test import attr
+from tempest import test
class RolesTestJSON(base.BaseIdentityV2AdminTest):
_interface = 'json'
@classmethod
+ @test.safe_setup
def setUpClass(cls):
super(RolesTestJSON, cls).setUpClass()
for _ in moves.xrange(5):
@@ -46,7 +47,7 @@
found = True
self.assertTrue(found, "assigned role was not in list")
- @attr(type='gate')
+ @test.attr(type='gate')
def test_list_roles(self):
# Return a list of all roles
resp, body = self.client.list_roles()
@@ -54,7 +55,7 @@
self.assertTrue(any(found))
self.assertEqual(len(found), len(self.data.roles))
- @attr(type='gate')
+ @test.attr(type='gate')
def test_role_create_delete(self):
# Role should be created, verified, and deleted
role_name = data_utils.rand_name(name='role-test-')
@@ -75,7 +76,7 @@
found = [role for role in body if role['name'] == role_name]
self.assertFalse(any(found))
- @attr(type='gate')
+ @test.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()
@@ -83,7 +84,7 @@
resp, roles = self.client.list_user_roles(tenant['id'], user['id'])
self.assert_role_in_role_list(role, roles)
- @attr(type='gate')
+ @test.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()
@@ -93,7 +94,7 @@
user_role['id'])
self.assertEqual(resp['status'], '204')
- @attr(type='gate')
+ @test.attr(type='gate')
def test_list_user_roles(self):
# List roles assigned to a user on tenant
(user, tenant, role) = self._get_role_params()
diff --git a/tempest/api/identity/admin/test_tokens.py b/tempest/api/identity/admin/test_tokens.py
index 239433b..c931bcf 100644
--- a/tempest/api/identity/admin/test_tokens.py
+++ b/tempest/api/identity/admin/test_tokens.py
@@ -22,7 +22,7 @@
_interface = 'json'
@attr(type='gate')
- def test_create_delete_token(self):
+ def test_create_get_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-')
@@ -43,11 +43,62 @@
self.assertEqual(rsp['status'], '200')
self.assertEqual(body['token']['tenant']['name'],
tenant['name'])
- # then delete the token
+ # Perform GET Token
token_id = body['token']['id']
+ resp, token_details = self.client.get_token(token_id)
+ self.assertEqual(resp['status'], '200')
+ self.assertEqual(token_id, token_details['token']['id'])
+ self.assertEqual(user['id'], token_details['user']['id'])
+ self.assertEqual(user_name, token_details['user']['name'])
+ self.assertEqual(tenant['name'],
+ token_details['token']['tenant']['name'])
+ # then delete the token
resp, body = self.client.delete_token(token_id)
self.assertEqual(resp['status'], '204')
+ @attr(type='gate')
+ def test_rescope_token(self):
+ """An unscoped token can be requested, that token can be used to
+ request a scoped token.
+ """
+
+ # Create a user.
+ user_name = data_utils.rand_name(name='user-')
+ user_password = data_utils.rand_name(name='pass-')
+ tenant_id = None # No default tenant so will get unscoped token.
+ email = ''
+ resp, user = self.client.create_user(user_name, user_password,
+ tenant_id, email)
+ self.assertEqual(200, resp.status)
+ self.data.users.append(user)
+
+ # 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)
+
+ # Create a role
+ role_name = data_utils.rand_name(name='role-')
+ resp, role = self.client.create_role(role_name)
+ self.assertEqual(200, resp.status)
+ self.data.roles.append(role)
+
+ # Grant the user the role on the tenant.
+ resp, _ = self.client.assign_user_role(tenant['id'], user['id'],
+ role['id'])
+ self.assertEqual(200, resp.status)
+
+ # Get an unscoped token.
+ rsp, body = self.token_client.auth(user_name, user_password)
+ self.assertEqual(200, resp.status)
+
+ token_id = body['token']['id']
+
+ # Use the unscoped token to get a scoped token.
+ rsp, body = self.token_client.auth_token(token_id, tenant=tenant_name)
+ self.assertEqual(200, resp.status)
+
class TokensTestXML(TokensTestJSON):
_interface = 'xml'
diff --git a/tempest/api/identity/admin/test_users_negative.py b/tempest/api/identity/admin/test_users_negative.py
index 1188325..4e8ebe5 100644
--- a/tempest/api/identity/admin/test_users_negative.py
+++ b/tempest/api/identity/admin/test_users_negative.py
@@ -13,11 +13,12 @@
# 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
-import uuid
class UsersNegativeTestJSON(base.BaseIdentityV2AdminTest):
diff --git a/tempest/api/identity/admin/v3/test_credentials.py b/tempest/api/identity/admin/v3/test_credentials.py
index 5f22d43..4442f4a 100644
--- a/tempest/api/identity/admin/v3/test_credentials.py
+++ b/tempest/api/identity/admin/v3/test_credentials.py
@@ -15,13 +15,14 @@
from tempest.api.identity import base
from tempest.common.utils import data_utils
-from tempest.test import attr
+from tempest import test
class CredentialsTestJSON(base.BaseIdentityV3AdminTest):
_interface = 'json'
@classmethod
+ @test.safe_setup
def setUpClass(cls):
super(CredentialsTestJSON, cls).setUpClass()
cls.projects = list()
@@ -56,7 +57,7 @@
resp, body = self.creds_client.delete_credential(cred_id)
self.assertEqual(resp['status'], '204')
- @attr(type='smoke')
+ @test.attr(type='smoke')
def test_credentials_create_get_update_delete(self):
keys = [data_utils.rand_name('Access-'),
data_utils.rand_name('Secret-')]
@@ -91,7 +92,7 @@
self.assertEqual(update_body['blob'][value2],
get_body['blob'][value2])
- @attr(type='smoke')
+ @test.attr(type='smoke')
def test_credentials_list_delete(self):
created_cred_ids = list()
fetched_cred_ids = list()
diff --git a/tempest/api/identity/admin/v3/test_endpoints.py b/tempest/api/identity/admin/v3/test_endpoints.py
index 05b704f..dd3b576 100644
--- a/tempest/api/identity/admin/v3/test_endpoints.py
+++ b/tempest/api/identity/admin/v3/test_endpoints.py
@@ -15,13 +15,14 @@
from tempest.api.identity import base
from tempest.common.utils import data_utils
-from tempest.test import attr
+from tempest import test
class EndPointsTestJSON(base.BaseIdentityV3AdminTest):
_interface = 'json'
@classmethod
+ @test.safe_setup
def setUpClass(cls):
super(EndPointsTestJSON, cls).setUpClass()
cls.identity_client = cls.client
@@ -53,7 +54,7 @@
cls.service_client.delete_service(s)
super(EndPointsTestJSON, cls).tearDownClass()
- @attr(type='gate')
+ @test.attr(type='gate')
def test_list_endpoints(self):
# Get a list of endpoints
resp, fetched_endpoints = self.client.list_endpoints()
@@ -65,7 +66,7 @@
"Failed to find endpoint %s in fetched list" %
', '.join(str(e) for e in missing_endpoints))
- @attr(type='gate')
+ @test.attr(type='gate')
def test_create_list_delete_endpoint(self):
region = data_utils.rand_name('region')
url = data_utils.rand_name('url')
@@ -91,7 +92,7 @@
fetched_endpoints_id = [e['id'] for e in fetched_endpoints]
self.assertNotIn(endpoint['id'], fetched_endpoints_id)
- @attr(type='smoke')
+ @test.attr(type='smoke')
def test_update_endpoint(self):
# Creating an endpoint so as to check update endpoint
# with new values
@@ -102,6 +103,7 @@
self.client.create_endpoint(self.service_id, interface1,
url1, region=region1,
enabled=True)
+ self.addCleanup(self.client.delete_endpoint, endpoint_for_update['id'])
# Creating service so as update endpoint with new service ID
s_name = data_utils.rand_name('service-')
s_type = data_utils.rand_name('type--')
@@ -126,7 +128,6 @@
self.assertEqual(url2, endpoint['url'])
self.assertEqual(region2, endpoint['region'])
self.assertEqual('false', str(endpoint['enabled']).lower())
- self.addCleanup(self.client.delete_endpoint, endpoint_for_update['id'])
class EndPointsTestXML(EndPointsTestJSON):
diff --git a/tempest/api/identity/admin/v3/test_endpoints_negative.py b/tempest/api/identity/admin/v3/test_endpoints_negative.py
new file mode 100644
index 0000000..28615a4
--- /dev/null
+++ b/tempest/api/identity/admin/v3/test_endpoints_negative.py
@@ -0,0 +1,94 @@
+
+# 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.identity import base
+from tempest.common.utils import data_utils
+from tempest import exceptions
+from tempest.test import attr
+
+
+class EndpointsNegativeTestJSON(base.BaseIdentityV3AdminTest):
+ _interface = 'json'
+
+ @classmethod
+ def setUpClass(cls):
+ super(EndpointsNegativeTestJSON, cls).setUpClass()
+ cls.identity_client = cls.client
+ cls.client = cls.endpoints_client
+ cls.service_ids = list()
+ 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.service_client.create_service(s_name, s_type,
+ description=s_description))
+ cls.service_id = cls.service_data['id']
+ cls.service_ids.append(cls.service_id)
+
+ @classmethod
+ def tearDownClass(cls):
+ for s in cls.service_ids:
+ cls.service_client.delete_service(s)
+ super(EndpointsNegativeTestJSON, cls).tearDownClass()
+
+ @attr(type=['negative', 'gate'])
+ def test_create_with_enabled_False(self):
+ # Enabled should be a boolean, not a string like 'False'
+ interface = 'public'
+ url = data_utils.rand_name('url')
+ region = data_utils.rand_name('region')
+ self.assertRaises(exceptions.BadRequest, self.client.create_endpoint,
+ self.service_id, interface, url, region=region,
+ force_enabled='False')
+
+ @attr(type=['negative', 'gate'])
+ def test_create_with_enabled_True(self):
+ # Enabled should be a boolean, not a string like 'True'
+ interface = 'public'
+ url = data_utils.rand_name('url')
+ region = data_utils.rand_name('region')
+ self.assertRaises(exceptions.BadRequest, self.client.create_endpoint,
+ self.service_id, interface, url, region=region,
+ force_enabled='True')
+
+ def _assert_update_raises_bad_request(self, enabled):
+
+ # Create an endpoint
+ 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))
+ self.addCleanup(self.client.delete_endpoint, endpoint_for_update['id'])
+
+ self.assertRaises(exceptions.BadRequest, self.client.update_endpoint,
+ endpoint_for_update['id'], force_enabled=enabled)
+
+ @attr(type=['negative', 'gate'])
+ def test_update_with_enabled_False(self):
+ # Enabled should be a boolean, not a string like 'False'
+ self._assert_update_raises_bad_request('False')
+
+ @attr(type=['negative', 'gate'])
+ def test_update_with_enabled_True(self):
+ # Enabled should be a boolean, not a string like 'True'
+ self._assert_update_raises_bad_request('True')
+
+
+class EndpointsNegativeTestXML(EndpointsNegativeTestJSON):
+ _interface = 'xml'
diff --git a/tempest/api/identity/admin/v3/test_groups.py b/tempest/api/identity/admin/v3/test_groups.py
index 6e898b2..056f713 100644
--- a/tempest/api/identity/admin/v3/test_groups.py
+++ b/tempest/api/identity/admin/v3/test_groups.py
@@ -68,7 +68,7 @@
# list users in group
resp, group_users = self.client.list_group_users(group['id'])
self.assertEqual(resp['status'], '200')
- self.assertEqual(users.sort(), group_users.sort())
+ self.assertEqual(sorted(users), sorted(group_users))
# delete user in group
for user in users:
resp, body = self.client.delete_group_user(group['id'],
@@ -77,6 +77,27 @@
resp, group_users = self.client.list_group_users(group['id'])
self.assertEqual(len(group_users), 0)
+ @test.attr(type='smoke')
+ def test_list_user_groups(self):
+ # create a user
+ resp, user = self.client.create_user(
+ data_utils.rand_name('User-'),
+ password=data_utils.rand_name('Pass-'))
+ self.addCleanup(self.client.delete_user, user['id'])
+ # create two groups, and add user into them
+ groups = []
+ for i in range(2):
+ name = data_utils.rand_name('Group-')
+ resp, group = self.client.create_group(name)
+ groups.append(group)
+ self.addCleanup(self.client.delete_group, group['id'])
+ self.client.add_group_user(group['id'], user['id'])
+ # list groups which user belongs to
+ resp, user_groups = self.client.list_user_groups(user['id'])
+ self.assertEqual('200', resp['status'])
+ self.assertEqual(sorted(groups), sorted(user_groups))
+ self.assertEqual(2, len(user_groups))
+
class GroupsV3TestXML(GroupsV3TestJSON):
_interface = 'xml'
diff --git a/tempest/api/identity/admin/v3/test_roles.py b/tempest/api/identity/admin/v3/test_roles.py
index 467d28b..c63231f 100644
--- a/tempest/api/identity/admin/v3/test_roles.py
+++ b/tempest/api/identity/admin/v3/test_roles.py
@@ -15,13 +15,14 @@
from tempest.api.identity import base
from tempest.common.utils import data_utils
-from tempest.test import attr
+from tempest import test
class RolesV3TestJSON(base.BaseIdentityV3AdminTest):
_interface = 'json'
@classmethod
+ @test.safe_setup
def setUpClass(cls):
super(RolesV3TestJSON, cls).setUpClass()
cls.fetched_role_ids = list()
@@ -68,7 +69,7 @@
self.assertEqual(len(body), 1)
self.assertIn(role_id, fetched_role_ids)
- @attr(type='smoke')
+ @test.attr(type='smoke')
def test_role_create_update_get(self):
r_name = data_utils.rand_name('Role-')
resp, role = self.client.create_role(r_name)
@@ -90,7 +91,7 @@
self.assertEqual(new_name, new_role['name'])
self.assertEqual(updated_role['id'], new_role['id'])
- @attr(type='smoke')
+ @test.attr(type='smoke')
def test_grant_list_revoke_role_to_user_on_project(self):
resp, _ = self.client.assign_user_role_on_project(
self.project['id'], self.user_body['id'], self.role['id'])
@@ -109,7 +110,7 @@
self.project['id'], self.user_body['id'], self.role['id'])
self.assertEqual(resp['status'], '204')
- @attr(type='smoke')
+ @test.attr(type='smoke')
def test_grant_list_revoke_role_to_user_on_domain(self):
resp, _ = self.client.assign_user_role_on_domain(
self.domain['id'], self.user_body['id'], self.role['id'])
@@ -128,7 +129,7 @@
self.domain['id'], self.user_body['id'], self.role['id'])
self.assertEqual(resp['status'], '204')
- @attr(type='smoke')
+ @test.attr(type='smoke')
def test_grant_list_revoke_role_to_group_on_project(self):
resp, _ = self.client.assign_group_role_on_project(
self.project['id'], self.group_body['id'], self.role['id'])
@@ -147,7 +148,7 @@
self.project['id'], self.group_body['id'], self.role['id'])
self.assertEqual(resp['status'], '204')
- @attr(type='smoke')
+ @test.attr(type='smoke')
def test_grant_list_revoke_role_to_group_on_domain(self):
resp, _ = self.client.assign_group_role_on_domain(
self.domain['id'], self.group_body['id'], self.role['id'])
diff --git a/tempest/api/identity/admin/v3/test_users.py b/tempest/api/identity/admin/v3/test_users.py
index a78d542..e1d1543 100644
--- a/tempest/api/identity/admin/v3/test_users.py
+++ b/tempest/api/identity/admin/v3/test_users.py
@@ -120,6 +120,13 @@
', '.join(m_project for m_project
in missing_projects))
+ @attr(type='gate')
+ def test_get_user(self):
+ # Get a user detail
+ self.data.setup_test_v3_user()
+ resp, user = self.client.get_user(self.data.v3_user['id'])
+ self.assertEqual(self.data.v3_user['id'], user['id'])
+
class UsersV3TestXML(UsersV3TestJSON):
_interface = 'xml'
diff --git a/tempest/api/identity/base.py b/tempest/api/identity/base.py
index 10f5217..a5bf248 100644
--- a/tempest/api/identity/base.py
+++ b/tempest/api/identity/base.py
@@ -153,10 +153,11 @@
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,
- self.project['id'],
- self.test_email)
+ resp, self.v3_user = self.client.create_user(
+ self.test_user,
+ password=self.test_password,
+ project_id=self.project['id'],
+ email=self.test_email)
self.v3_users.append(self.v3_user)
def setup_test_project(self):
diff --git a/tempest/api/image/v1/test_image_members.py b/tempest/api/image/v1/test_image_members.py
index e6a078e..4cbb62f 100644
--- a/tempest/api/image/v1/test_image_members.py
+++ b/tempest/api/image/v1/test_image_members.py
@@ -14,12 +14,12 @@
from tempest.api.image import base
-from tempest.test import attr
+from tempest import test
class ImageMembersTest(base.BaseV1ImageMembersTest):
- @attr(type='gate')
+ @test.attr(type='gate')
def test_add_image_member(self):
image = self._create_image()
resp = self.client.add_member(self.alt_tenant_id, image)
@@ -33,7 +33,7 @@
resp, body = self.alt_img_cli.get_image(image)
self.assertEqual(200, resp.status)
- @attr(type='gate')
+ @test.attr(type='gate')
def test_get_shared_images(self):
image = self._create_image()
resp = self.client.add_member(self.alt_tenant_id, image)
@@ -48,7 +48,7 @@
self.assertIn(share_image, images)
self.assertIn(image, images)
- @attr(type='gate')
+ @test.attr(type='gate')
def test_remove_member(self):
image_id = self._create_image()
resp = self.client.add_member(self.alt_tenant_id, image_id)
diff --git a/tempest/api/image/v1/test_image_members_negative.py b/tempest/api/image/v1/test_image_members_negative.py
index d68ef03..aac63b4 100644
--- a/tempest/api/image/v1/test_image_members_negative.py
+++ b/tempest/api/image/v1/test_image_members_negative.py
@@ -16,26 +16,26 @@
from tempest.api.image import base
from tempest.common.utils import data_utils
from tempest import exceptions
-from tempest.test import attr
+from tempest import test
class ImageMembersNegativeTest(base.BaseV1ImageMembersTest):
- @attr(type=['negative', 'gate'])
+ @test.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'])
+ @test.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'])
+ @test.attr(type=['negative', 'gate'])
def test_delete_member_with_non_existing_tenant(self):
# Delete member with non existing tenant.
image_id = self._create_image()
@@ -43,7 +43,7 @@
self.assertRaises(exceptions.NotFound, self.client.delete_member,
non_exist_tenant, image_id)
- @attr(type=['negative', 'gate'])
+ @test.attr(type=['negative', 'gate'])
def test_get_image_without_membership(self):
# Image is hidden from another tenants.
image_id = self._create_image()
diff --git a/tempest/api/image/v1/test_images_negative.py b/tempest/api/image/v1/test_images_negative.py
index 5695884..66556e0 100644
--- a/tempest/api/image/v1/test_images_negative.py
+++ b/tempest/api/image/v1/test_images_negative.py
@@ -15,30 +15,30 @@
from tempest.api.image import base
from tempest import exceptions
-from tempest.test import attr
+from tempest import test
class CreateDeleteImagesNegativeTest(base.BaseV1ImageTest):
"""Here are negative tests for the deletion and creation of images."""
- @attr(type=['negative', 'gate'])
+ @test.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'])
+ @test.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'])
+ @test.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'])
+ @test.attr(type=['negative', 'gate'])
def test_delete_non_existent_image(self):
# Return an error while trying to delete a non-existent image
@@ -46,24 +46,24 @@
self.assertRaises(exceptions.NotFound, self.client.delete_image,
non_existent_image_id)
- @attr(type=['negative', 'gate'])
+ @test.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'])
+ @test.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'])
+ @test.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'])
+ @test.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,
diff --git a/tempest/api/image/v2/test_images.py b/tempest/api/image/v2/test_images.py
index d448c01..abde8f7 100644
--- a/tempest/api/image/v2/test_images.py
+++ b/tempest/api/image/v2/test_images.py
@@ -19,7 +19,7 @@
from tempest.api.image import base
from tempest.common.utils import data_utils
-from tempest.test import attr
+from tempest import test
class BasicOperationsImagesTest(base.BaseV2ImageTest):
@@ -27,7 +27,7 @@
Here we test the basic operations of images
"""
- @attr(type='gate')
+ @test.attr(type='gate')
def test_register_upload_get_image_file(self):
"""
@@ -68,7 +68,7 @@
self.assertEqual(200, resp.status)
self.assertEqual(file_content, body)
- @attr(type='gate')
+ @test.attr(type='gate')
def test_delete_image(self):
# Deletes an image by image_id
@@ -90,7 +90,7 @@
self.assertEqual(resp.status, 200)
self.assertNotIn(image_id, images)
- @attr(type='gate')
+ @test.attr(type='gate')
def test_update_image(self):
# Updates an image by image_id
@@ -101,6 +101,7 @@
disk_format='iso',
visibility='public')
self.assertEqual(201, resp.status)
+ self.addCleanup(self.client.delete_image, body['id'])
self.assertEqual('queued', body['status'])
image_id = body['id']
@@ -176,7 +177,7 @@
msg = "Failed to list images by %s" % key
self.assertEqual(params[key], image[key], msg)
- @attr(type='gate')
+ @test.attr(type='gate')
def test_index_no_params(self):
# Simple test to see all fixture images returned
resp, images_list = self.client.image_list()
@@ -186,25 +187,25 @@
for image in self.created_images:
self.assertIn(image, image_list)
- @attr(type='gate')
+ @test.attr(type='gate')
def test_list_images_param_container_format(self):
# Test to get all images with container_format='bare'
params = {"container_format": "bare"}
self._list_by_param_value_and_assert(params)
- @attr(type='gate')
+ @test.attr(type='gate')
def test_list_images_param_disk_format(self):
# Test to get all images with disk_format = raw
params = {"disk_format": "raw"}
self._list_by_param_value_and_assert(params)
- @attr(type='gate')
+ @test.attr(type='gate')
def test_list_images_param_visibility(self):
# Test to get all images with visibility = public
params = {"visibility": "public"}
self._list_by_param_value_and_assert(params)
- @attr(type='gate')
+ @test.attr(type='gate')
def test_list_images_param_size(self):
# Test to get all images by size
image_id = self.created_images[1]
@@ -215,7 +216,7 @@
params = {"size": image['size']}
self._list_by_param_value_and_assert(params)
- @attr(type='gate')
+ @test.attr(type='gate')
def test_list_images_param_min_max_size(self):
# Test to get all images with size between 2000 to 3000
image_id = self.created_images[1]
@@ -234,13 +235,13 @@
image_size <= params['size_max'],
"Failed to get images by size_min and size_max")
- @attr(type='gate')
+ @test.attr(type='gate')
def test_list_images_param_status(self):
# Test to get all active images
params = {"status": "active"}
self._list_by_param_value_and_assert(params)
- @attr(type='gate')
+ @test.attr(type='gate')
def test_list_images_param_limit(self):
# Test to get images by limit
params = {"limit": 2}
@@ -250,7 +251,7 @@
self.assertEqual(len(images_list), params['limit'],
"Failed to get images by limit")
- @attr(type='gate')
+ @test.attr(type='gate')
def test_get_image_schema(self):
# Test to get image schema
schema = "image"
@@ -258,7 +259,7 @@
self.assertEqual(200, resp.status)
self.assertEqual("image", body['name'])
- @attr(type='gate')
+ @test.attr(type='gate')
def test_get_images_schema(self):
# Test to get images schema
schema = "images"
diff --git a/tempest/api/image/v2/test_images_member.py b/tempest/api/image/v2/test_images_member.py
index 530262f..f80c818 100644
--- a/tempest/api/image/v2/test_images_member.py
+++ b/tempest/api/image/v2/test_images_member.py
@@ -11,13 +11,13 @@
# under the License.
from tempest.api.image import base
-from tempest.test import attr
+from tempest import test
class ImagesMemberTest(base.BaseV2MemberImageTest):
_interface = 'json'
- @attr(type='gate')
+ @test.attr(type='gate')
def test_image_share_accept(self):
image_id = self._create_image()
resp, member = self.os_img_client.add_member(image_id,
@@ -40,7 +40,7 @@
self.assertEqual(member['image_id'], image_id)
self.assertEqual(member['status'], 'accepted')
- @attr(type='gate')
+ @test.attr(type='gate')
def test_image_share_reject(self):
image_id = self._create_image()
resp, member = self.os_img_client.add_member(image_id,
@@ -56,7 +56,7 @@
self.assertEqual(200, resp.status)
self.assertNotIn(image_id, self._list_image_ids_as_alt())
- @attr(type='gate')
+ @test.attr(type='gate')
def test_get_image_member(self):
image_id = self._create_image()
self.os_img_client.add_member(image_id,
@@ -73,7 +73,7 @@
self.assertEqual(image_id, member['image_id'])
self.assertEqual('accepted', member['status'])
- @attr(type='gate')
+ @test.attr(type='gate')
def test_remove_image_member(self):
image_id = self._create_image()
self.os_img_client.add_member(image_id,
@@ -86,3 +86,15 @@
resp = self.os_img_client.remove_member(image_id, self.alt_tenant_id)
self.assertEqual(204, resp.status)
self.assertNotIn(image_id, self._list_image_ids_as_alt())
+
+ @test.attr(type='gate')
+ def test_get_image_member_schema(self):
+ resp, body = self.os_img_client.get_schema("member")
+ self.assertEqual(200, resp.status)
+ self.assertEqual("member", body['name'])
+
+ @test.attr(type='gate')
+ def test_get_image_members_schema(self):
+ resp, body = self.os_img_client.get_schema("members")
+ self.assertEqual(200, resp.status)
+ self.assertEqual("members", body['name'])
diff --git a/tempest/api/image/v2/test_images_member_negative.py b/tempest/api/image/v2/test_images_member_negative.py
index 4c7cc5a..98ef649 100644
--- a/tempest/api/image/v2/test_images_member_negative.py
+++ b/tempest/api/image/v2/test_images_member_negative.py
@@ -12,13 +12,13 @@
from tempest.api.image import base
from tempest import exceptions
-from tempest.test import attr
+from tempest import test
class ImagesMemberNegativeTest(base.BaseV2MemberImageTest):
_interface = 'json'
- @attr(type=['negative', 'gate'])
+ @test.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,
@@ -28,7 +28,7 @@
self.alt_img_client.update_member_status,
image_id, self.alt_tenant_id, 'notavalidstatus')
- @attr(type=['negative', 'gate'])
+ @test.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,
diff --git a/tempest/api/image/v2/test_images_negative.py b/tempest/api/image/v2/test_images_negative.py
index b8ba868..27ba39c 100644
--- a/tempest/api/image/v2/test_images_negative.py
+++ b/tempest/api/image/v2/test_images_negative.py
@@ -18,7 +18,7 @@
from tempest.api.image import base
from tempest import exceptions
-from tempest.test import attr
+from tempest import test
class ImagesNegativeTest(base.BaseV2ImageTest):
@@ -35,20 +35,20 @@
** delete the deleted image
"""
- @attr(type=['negative', 'gate'])
+ @test.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'])
+ @test.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'])
+ @test.attr(type=['negative', 'gate'])
def test_get_delete_deleted_image(self):
# get and delete the deleted image
# create and delete image
@@ -67,27 +67,27 @@
self.assertRaises(exceptions.NotFound, self.client.delete_image,
image_id)
- @attr(type=['negative', 'gate'])
+ @test.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'])
+ @test.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)
- @attr(type=['negative', 'gate'])
+ @test.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'])
+ @test.attr(type=['negative', 'gate'])
def test_register_with_invalid_disk_format(self):
self.assertRaises(exceptions.BadRequest, self.client.create_image,
'test', 'bare', 'wrong')
diff --git a/tempest/api/image/v2/test_images_tags.py b/tempest/api/image/v2/test_images_tags.py
index f0e343d..504c0e8 100644
--- a/tempest/api/image/v2/test_images_tags.py
+++ b/tempest/api/image/v2/test_images_tags.py
@@ -14,12 +14,12 @@
from tempest.api.image import base
from tempest.common.utils import data_utils
-from tempest.test import attr
+from tempest import test
class ImagesTagsTest(base.BaseV2ImageTest):
- @attr(type='gate')
+ @test.attr(type='gate')
def test_update_delete_tags_for_image(self):
resp, body = self.create_image(container_format='bare',
disk_format='raw',
diff --git a/tempest/api/image/v2/test_images_tags_negative.py b/tempest/api/image/v2/test_images_tags_negative.py
index 0628d29..3233db7 100644
--- a/tempest/api/image/v2/test_images_tags_negative.py
+++ b/tempest/api/image/v2/test_images_tags_negative.py
@@ -17,12 +17,12 @@
from tempest.api.image import base
from tempest.common.utils import data_utils
from tempest import exceptions
-from tempest.test import attr
+from tempest import test
class ImagesTagsNegativeTest(base.BaseV2ImageTest):
- @attr(type=['negative', 'gate'])
+ @test.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-')
@@ -30,7 +30,7 @@
self.assertRaises(exceptions.NotFound, self.client.add_image_tag,
non_exist_image, tag)
- @attr(type=['negative', 'gate'])
+ @test.attr(type=['negative', 'gate'])
def test_delete_non_existing_tag(self):
# Delete non existing tag.
resp, body = self.create_image(container_format='bare',
diff --git a/tempest/api/network/admin/test_dhcp_agent_scheduler.py b/tempest/api/network/admin/test_dhcp_agent_scheduler.py
index ecd992a..0e601d1 100644
--- a/tempest/api/network/admin/test_dhcp_agent_scheduler.py
+++ b/tempest/api/network/admin/test_dhcp_agent_scheduler.py
@@ -26,7 +26,7 @@
msg = "dhcp_agent_scheduler extension not enabled."
raise cls.skipException(msg)
# Create a network and make sure it will be hosted by a
- # dhcp agent.
+ # dhcp agent: this is done by creating a regular port
cls.network = cls.create_network()
cls.subnet = cls.create_subnet(cls.network)
cls.cidr = cls.subnet['cidr']
@@ -60,6 +60,9 @@
@test.attr(type='smoke')
def test_remove_network_from_dhcp_agent(self):
+ # The agent is now bound to the network, we can free the port
+ self.client.delete_port(self.port['id'])
+ self.ports.remove(self.port)
resp, body = self.admin_client.list_dhcp_agent_hosting_network(
self.network['id'])
agents = body['agents']
diff --git a/tempest/api/network/admin/test_external_network_extension.py b/tempest/api/network/admin/test_external_network_extension.py
new file mode 100644
index 0000000..c7fde77
--- /dev/null
+++ b/tempest/api/network/admin/test_external_network_extension.py
@@ -0,0 +1,94 @@
+# 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 ExternalNetworksTestJSON(base.BaseAdminNetworkTest):
+ _interface = 'json'
+
+ @classmethod
+ def setUpClass(cls):
+ super(ExternalNetworksTestJSON, cls).setUpClass()
+ cls.network = cls.create_network()
+
+ def _create_network(self, external=True):
+ post_body = {'name': data_utils.rand_name('network-')}
+ if external:
+ post_body['router:external'] = external
+ resp, body = self.admin_client.create_network(**post_body)
+ network = body['network']
+ self.assertEqual('201', resp['status'])
+ self.addCleanup(self.admin_client.delete_network, network['id'])
+ return network
+
+ def test_create_external_network(self):
+ # Create a network as an admin user specifying the
+ # external network extension attribute
+ ext_network = self._create_network()
+ # Verifies router:external parameter
+ self.assertIsNotNone(ext_network['id'])
+ self.assertTrue(ext_network['router:external'])
+
+ def test_update_external_network(self):
+ # Update a network as an admin user specifying the
+ # external network extension attribute
+ network = self._create_network(external=False)
+ self.assertFalse(network.get('router:external', False))
+ update_body = {'router:external': True}
+ resp, body = self.admin_client.update_network(network['id'],
+ **update_body)
+ self.assertEqual('200', resp['status'])
+ updated_network = body['network']
+ # Verify that router:external parameter was updated
+ self.assertTrue(updated_network['router:external'])
+
+ def test_list_external_networks(self):
+ # Create external_net
+ external_network = self._create_network()
+ # List networks as a normal user and confirm the external
+ # network extension attribute is returned for those networks
+ # that were created as external
+ resp, body = self.client.list_networks()
+ self.assertEqual('200', resp['status'])
+ networks_list = [net['id'] for net in body['networks']]
+ self.assertIn(external_network['id'], networks_list)
+ self.assertIn(self.network['id'], networks_list)
+ for net in body['networks']:
+ if net['id'] == self.network['id']:
+ self.assertFalse(net['router:external'])
+ elif net['id'] == external_network['id']:
+ self.assertTrue(net['router:external'])
+
+ def test_show_external_networks_attribute(self):
+ # Create external_net
+ external_network = self._create_network()
+ # Show an external network as a normal user and confirm the
+ # external network extension attribute is returned.
+ resp, body = self.client.show_network(external_network['id'])
+ self.assertEqual('200', resp['status'])
+ show_ext_net = body['network']
+ self.assertEqual(external_network['name'], show_ext_net['name'])
+ self.assertEqual(external_network['id'], show_ext_net['id'])
+ self.assertTrue(show_ext_net['router:external'])
+ resp, body = self.client.show_network(self.network['id'])
+ self.assertEqual('200', resp['status'])
+ show_net = body['network']
+ # Verify with show that router:external is False for network
+ self.assertEqual(self.network['name'], show_net['name'])
+ self.assertEqual(self.network['id'], show_net['id'])
+ self.assertFalse(show_net['router:external'])
+
+
+class ExternalNetworksTestXML(ExternalNetworksTestJSON):
+ _interface = 'xml'
diff --git a/tempest/api/network/admin/test_l3_agent_scheduler.py b/tempest/api/network/admin/test_l3_agent_scheduler.py
index 7c02787..f4050c5 100644
--- a/tempest/api/network/admin/test_l3_agent_scheduler.py
+++ b/tempest/api/network/admin/test_l3_agent_scheduler.py
@@ -26,6 +26,7 @@
List routers that the given L3 agent is hosting.
List L3 agents hosting the given router.
+ Add and Remove Router to L3 agent
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:
@@ -37,28 +38,46 @@
if not test.is_extension_enabled('l3_agent_scheduler', 'network'):
msg = "L3 Agent Scheduler Extension not enabled."
raise cls.skipException(msg)
+ # Trying to get agent details for L3 Agent
+ resp, body = cls.admin_client.list_agents()
+ agents = body['agents']
+ for agent in agents:
+ if agent['agent_type'] == 'L3 agent':
+ cls.agent = agent
+ break
+ else:
+ msg = "L3 Agent not found"
+ raise cls.skipException(msg)
@test.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.agent['id'])
self.assertEqual('200', resp['status'])
@test.attr(type='smoke')
- def test_list_l3_agents_hosting_router(self):
- name = data_utils.rand_name('router-')
+ def test_add_list_remove_router_on_l3_agent(self):
+ l3_agent_ids = list()
+ name = data_utils.rand_name('router1-')
resp, router = self.client.create_router(name)
+ self.addCleanup(self.client.delete_router, router['router']['id'])
+ resp, body = self.admin_client.add_router_to_l3_agent(
+ self.agent['id'], router['router']['id'])
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)
+ for agent in body['agents']:
+ l3_agent_ids.append(agent['id'])
+ self.assertIn('agent_type', agent)
+ self.assertEqual('L3 agent', agent['agent_type'])
+ self.assertIn(self.agent['id'], l3_agent_ids)
+ del l3_agent_ids[:]
+ resp, body = self.admin_client.remove_router_from_l3_agent(
+ self.agent['id'], router['router']['id'])
+ self.assertEqual('204', resp['status'])
+ # NOTE(afazekas): The deletion not asserted, because neutron
+ # is not forbidden to reschedule the router to the same agent
class L3AgentSchedulerTestXML(L3AgentSchedulerTestJSON):
diff --git a/tempest/api/network/admin/test_load_balancer_admin_actions.py b/tempest/api/network/admin/test_load_balancer_admin_actions.py
new file mode 100644
index 0000000..34a8e32
--- /dev/null
+++ b/tempest/api/network/admin/test_load_balancer_admin_actions.py
@@ -0,0 +1,94 @@
+# Copyright 2014 Mirantis.inc
+# 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 import test
+
+
+class LoadBalancerAdminTestJSON(base.BaseAdminNetworkTest):
+ _interface = 'json'
+
+ """
+ Test admin actions for load balancer.
+
+ Create VIP for another tenant
+ Create health monitor for another tenant
+ """
+
+ @classmethod
+ def setUpClass(cls):
+ super(LoadBalancerAdminTestJSON, cls).setUpClass()
+ if not test.is_extension_enabled('lbaas', 'network'):
+ msg = "lbaas extension not enabled."
+ raise cls.skipException(msg)
+ cls.force_tenant_isolation = True
+ manager = cls.get_client_manager()
+ cls.client = manager.network_client
+ username, tenant_name, passwd = cls.isolated_creds.get_primary_creds()
+ cls.tenant_id = cls.os_adm.identity_client.get_tenant_by_name(
+ tenant_name)['id']
+ cls.network = cls.create_network()
+ cls.subnet = cls.create_subnet(cls.network)
+
+ @test.attr(type='smoke')
+ def test_create_vip_as_admin_for_another_tenant(self):
+ name = data_utils.rand_name('vip-')
+ resp, body = self.admin_client.create_pool(
+ name=data_utils.rand_name('pool-'), lb_method="ROUND_ROBIN",
+ protocol="HTTP", subnet_id=self.subnet['id'],
+ tenant_id=self.tenant_id)
+ self.assertEqual('201', resp['status'])
+ pool = body['pool']
+ self.addCleanup(self.admin_client.delete_pool, pool['id'])
+ resp, body = self.admin_client.create_vip(name=name,
+ protocol="HTTP",
+ protocol_port=80,
+ subnet_id=self.subnet['id'],
+ pool_id=pool['id'],
+ tenant_id=self.tenant_id)
+ self.assertEqual('201', resp['status'])
+ vip = body['vip']
+ self.addCleanup(self.admin_client.delete_vip, vip['id'])
+ self.assertIsNotNone(vip['id'])
+ self.assertEqual(self.tenant_id, vip['tenant_id'])
+ resp, body = self.client.show_vip(vip['id'])
+ self.assertEqual('200', resp['status'])
+ show_vip = body['vip']
+ self.assertEqual(vip['id'], show_vip['id'])
+ self.assertEqual(vip['name'], show_vip['name'])
+
+ @test.attr(type='smoke')
+ def test_create_health_monitor_as_admin_for_another_tenant(self):
+ resp, body = (
+ self.admin_client.create_health_monitor(delay=4,
+ max_retries=3,
+ type="TCP",
+ timeout=1,
+ tenant_id=self.tenant_id))
+ self.assertEqual('201', resp['status'])
+ health_monitor = body['health_monitor']
+ self.addCleanup(self.admin_client.delete_health_monitor,
+ health_monitor['id'])
+ self.assertIsNotNone(health_monitor['id'])
+ self.assertEqual(self.tenant_id, health_monitor['tenant_id'])
+ resp, body = self.client.show_health_monitor(health_monitor['id'])
+ self.assertEqual('200', resp['status'])
+ show_health_monitor = body['health_monitor']
+ self.assertEqual(health_monitor['id'], show_health_monitor['id'])
+
+
+class LoadBalancerAdminTestXML(LoadBalancerAdminTestJSON):
+ _interface = 'xml'
diff --git a/tempest/api/network/base.py b/tempest/api/network/base.py
index 93335e7..3503aa0 100644
--- a/tempest/api/network/base.py
+++ b/tempest/api/network/base.py
@@ -242,14 +242,20 @@
@classmethod
def create_member(cls, protocol_port, pool):
"""Wrapper utility that returns a test member."""
- resp, body = cls.client.create_member("10.0.9.46",
- protocol_port,
- pool['id'])
+ resp, body = cls.client.create_member(address="10.0.9.46",
+ protocol_port=protocol_port,
+ pool_id=pool['id'])
member = body['member']
cls.members.append(member)
return member
@classmethod
+ def update_member(cls, admin_state_up):
+ resp, body = cls.client.update_member(admin_state_up=admin_state_up)
+ member = body['member']
+ return member
+
+ @classmethod
def create_health_monitor(cls, delay, max_retries, Type, timeout):
"""Wrapper utility that returns a test health monitor."""
resp, body = cls.client.create_health_monitor(delay=delay,
@@ -271,6 +277,7 @@
"""Wrapper utility that returns a router interface."""
resp, interface = cls.client.add_router_interface_with_subnet_id(
router_id, subnet_id)
+ return interface
@classmethod
def create_vpnservice(cls, subnet_id, router_id):
diff --git a/tempest/api/network/common.py b/tempest/api/network/common.py
index d68ff1a..97e120f 100644
--- a/tempest/api/network/common.py
+++ b/tempest/api/network/common.py
@@ -61,6 +61,11 @@
super(DeletableSubnet, self).__init__(*args, **kwargs)
self._router_ids = set()
+ def update(self, *args, **kwargs):
+ body = dict(subnet=dict(*args, **kwargs))
+ result = self.client.update_subnet(subnet=self.id, body=body)
+ super(DeletableSubnet, self).update(**result['subnet'])
+
def add_to_router(self, router_id):
self._router_ids.add(router_id)
body = dict(subnet_id=self.id)
diff --git a/tempest/api/network/test_load_balancer.py b/tempest/api/network/test_load_balancer.py
index 03e095d..792d61d 100644
--- a/tempest/api/network/test_load_balancer.py
+++ b/tempest/api/network/test_load_balancer.py
@@ -61,22 +61,54 @@
Type="TCP",
timeout=1)
+ def _check_list_with_filter(self, obj_name, attr_exceptions, **kwargs):
+ create_obj = getattr(self.client, 'create_' + obj_name)
+ delete_obj = getattr(self.client, 'delete_' + obj_name)
+ list_objs = getattr(self.client, 'list_' + obj_name + 's')
+
+ resp, body = create_obj(**kwargs)
+ self.assertEqual('201', resp['status'])
+ obj = body[obj_name]
+ self.addCleanup(delete_obj, obj['id'])
+ for key, value in obj.iteritems():
+ # It is not relevant to filter by all arguments. That is why
+ # there is a list of attr to except
+ if key not in attr_exceptions:
+ resp, body = list_objs(**{key: value})
+ self.assertEqual('200', resp['status'])
+ objs = [v[key] for v in body[obj_name + 's']]
+ self.assertIn(value, objs)
+
@test.attr(type='smoke')
def test_list_vips(self):
# Verify the vIP exists in the list of all vIPs
resp, body = self.client.list_vips()
self.assertEqual('200', resp['status'])
vips = body['vips']
- found = None
- for n in vips:
- if (n['id'] == self.vip['id']):
- found = n['id']
- msg = "vIPs list doesn't contain created vip"
- self.assertIsNotNone(found, msg)
+ self.assertIn(self.vip['id'], [v['id'] for v in vips])
+ @test.attr(type='smoke')
+ def test_list_vips_with_filter(self):
+ name = data_utils.rand_name('vip-')
+ resp, body = self.client.create_pool(
+ name=data_utils.rand_name("pool-"), lb_method="ROUND_ROBIN",
+ protocol="HTTPS", subnet_id=self.subnet['id'])
+ self.assertEqual('201', resp['status'])
+ pool = body['pool']
+ self.addCleanup(self.client.delete_pool, pool['id'])
+ attr_exceptions = ['status', 'session_persistence',
+ 'status_description']
+ self._check_list_with_filter(
+ 'vip', attr_exceptions, name=name, protocol="HTTPS",
+ protocol_port=81, subnet_id=self.subnet['id'], pool_id=pool['id'],
+ description=data_utils.rand_name('description-'),
+ admin_state_up=False)
+
+ @test.attr(type='smoke')
def test_create_update_delete_pool_vip(self):
# Creates a vip
name = data_utils.rand_name('vip-')
+ address = self.subnet['allocation_pools'][0]['end']
resp, body = self.client.create_pool(
name=data_utils.rand_name("pool-"),
lb_method='ROUND_ROBIN',
@@ -87,19 +119,40 @@
protocol="HTTP",
protocol_port=80,
subnet_id=self.subnet['id'],
- pool_id=pool['id'])
+ pool_id=pool['id'],
+ address=address)
self.assertEqual('201', resp['status'])
vip = body['vip']
vip_id = vip['id']
+ # Confirm VIP's address correctness with a show
+ resp, body = self.client.show_vip(vip_id)
+ self.assertEqual('200', resp['status'])
+ vip = body['vip']
+ self.assertEqual(address, vip['address'])
# Verification of vip update
new_name = "New_vip"
- resp, body = self.client.update_vip(vip_id, name=new_name)
+ new_description = "New description"
+ persistence_type = "HTTP_COOKIE"
+ update_data = {"session_persistence": {
+ "type": persistence_type}}
+ resp, body = self.client.update_vip(vip_id,
+ name=new_name,
+ description=new_description,
+ connection_limit=10,
+ admin_state_up=False,
+ **update_data)
self.assertEqual('200', resp['status'])
updated_vip = body['vip']
- self.assertEqual(updated_vip['name'], new_name)
+ self.assertEqual(new_name, updated_vip['name'])
+ self.assertEqual(new_description, updated_vip['description'])
+ self.assertEqual(10, updated_vip['connection_limit'])
+ self.assertFalse(updated_vip['admin_state_up'])
+ self.assertEqual(persistence_type,
+ updated_vip['session_persistence']['type'])
# Verification of vip delete
resp, body = self.client.delete_vip(vip['id'])
self.assertEqual('204', resp['status'])
+ self.client.wait_for_resource_deletion('vip', vip['id'])
# Verification of pool update
new_name = "New_pool"
resp, body = self.client.update_pool(pool['id'],
@@ -117,17 +170,30 @@
resp, body = self.client.show_vip(self.vip['id'])
self.assertEqual('200', resp['status'])
vip = body['vip']
- self.assertEqual(self.vip['id'], vip['id'])
- self.assertEqual(self.vip['name'], vip['name'])
+ for key, value in vip.iteritems():
+ # 'status' should not be confirmed in api tests
+ if key != 'status':
+ self.assertEqual(self.vip[key], value)
@test.attr(type='smoke')
def test_show_pool(self):
- # Verifies the details of a pool
- resp, body = self.client.show_pool(self.pool['id'])
- self.assertEqual('200', resp['status'])
+ # Here we need to new pool without any dependence with vips
+ resp, body = self.client.create_pool(
+ name=data_utils.rand_name("pool-"),
+ lb_method='ROUND_ROBIN',
+ protocol='HTTP',
+ subnet_id=self.subnet['id'])
+ self.assertEqual('201', resp['status'])
pool = body['pool']
- self.assertEqual(self.pool['id'], pool['id'])
- self.assertEqual(self.pool['name'], pool['name'])
+ self.addCleanup(self.client.delete_pool, pool['id'])
+ # Verifies the details of a pool
+ resp, body = self.client.show_pool(pool['id'])
+ self.assertEqual('200', resp['status'])
+ shown_pool = body['pool']
+ for key, value in pool.iteritems():
+ # 'status' should not be confirmed in api tests
+ if key != 'status':
+ self.assertEqual(value, shown_pool[key])
@test.attr(type='smoke')
def test_list_pools(self):
@@ -138,6 +204,17 @@
self.assertIn(self.pool['id'], [p['id'] for p in pools])
@test.attr(type='smoke')
+ def test_list_pools_with_filters(self):
+ attr_exceptions = ['status', 'vip_id', 'members', 'provider',
+ 'status_description']
+ self._check_list_with_filter(
+ 'pool', attr_exceptions, name=data_utils.rand_name("pool-"),
+ lb_method="ROUND_ROBIN", protocol="HTTPS",
+ subnet_id=self.subnet['id'],
+ description=data_utils.rand_name('description-'),
+ admin_state_up=False)
+
+ @test.attr(type='smoke')
def test_list_members(self):
# Verify the member exists in the list of all members
resp, body = self.client.list_members()
@@ -146,14 +223,23 @@
self.assertIn(self.member['id'], [m['id'] for m in members])
@test.attr(type='smoke')
+ def test_list_members_with_filters(self):
+ attr_exceptions = ['status', 'status_description']
+ self._check_list_with_filter('member', attr_exceptions,
+ address="10.0.9.47", protocol_port=80,
+ pool_id=self.pool['id'])
+
+ @test.attr(type='smoke')
def test_create_update_delete_member(self):
# Creates a member
- resp, body = self.client.create_member("10.0.9.47", 80,
- self.pool['id'])
+ resp, body = self.client.create_member(address="10.0.9.47",
+ protocol_port=80,
+ pool_id=self.pool['id'])
self.assertEqual('201', resp['status'])
member = body['member']
# Verification of member update
- resp, body = self.client.update_member(False, member['id'])
+ resp, body = self.client.update_member(member['id'],
+ admin_state_up=False)
self.assertEqual('200', resp['status'])
updated_member = body['member']
self.assertFalse(updated_member['admin_state_up'])
@@ -167,9 +253,10 @@
resp, body = self.client.show_member(self.member['id'])
self.assertEqual('200', resp['status'])
member = body['member']
- self.assertEqual(self.member['id'], member['id'])
- self.assertEqual(self.member['admin_state_up'],
- member['admin_state_up'])
+ for key, value in member.iteritems():
+ # 'status' should not be confirmed in api tests
+ if key != 'status':
+ self.assertEqual(self.member[key], value)
@test.attr(type='smoke')
def test_list_health_monitors(self):
@@ -181,6 +268,13 @@
[h['id'] for h in health_monitors])
@test.attr(type='smoke')
+ def test_list_health_monitors_with_filters(self):
+ attr_exceptions = ['status', 'status_description', 'pools']
+ self._check_list_with_filter('health_monitor', attr_exceptions,
+ delay=5, max_retries=4, type="TCP",
+ timeout=2)
+
+ @test.attr(type='smoke')
def test_create_update_delete_health_monitor(self):
# Creates a health_monitor
resp, body = self.client.create_health_monitor(delay=4,
@@ -201,14 +295,49 @@
self.assertEqual('204', resp['status'])
@test.attr(type='smoke')
+ def test_create_health_monitor_http_type(self):
+ hm_type = "HTTP"
+ resp, body = self.client.create_health_monitor(delay=4,
+ max_retries=3,
+ type=hm_type,
+ timeout=1)
+ self.assertEqual('201', resp['status'])
+ health_monitor = body['health_monitor']
+ self.addCleanup(self.client.delete_health_monitor,
+ health_monitor['id'])
+ self.assertEqual(hm_type, health_monitor['type'])
+
+ @test.attr(type='smoke')
+ def test_update_health_monitor_http_method(self):
+ resp, body = self.client.create_health_monitor(delay=4,
+ max_retries=3,
+ type="HTTP",
+ timeout=1)
+ self.assertEqual('201', resp['status'])
+ health_monitor = body['health_monitor']
+ self.addCleanup(self.client.delete_health_monitor,
+ health_monitor['id'])
+ resp, body = (self.client.update_health_monitor
+ (health_monitor['id'],
+ http_method="POST",
+ url_path="/home/user",
+ expected_codes="290"))
+ self.assertEqual('200', resp['status'])
+ updated_health_monitor = body['health_monitor']
+ self.assertEqual("POST", updated_health_monitor['http_method'])
+ self.assertEqual("/home/user", updated_health_monitor['url_path'])
+ self.assertEqual("290", updated_health_monitor['expected_codes'])
+
+ @test.attr(type='smoke')
def test_show_health_monitor(self):
# Verifies the details of a health_monitor
resp, body = self.client.show_health_monitor(self.health_monitor['id'])
self.assertEqual('200', resp['status'])
health_monitor = body['health_monitor']
- self.assertEqual(self.health_monitor['id'], health_monitor['id'])
- self.assertEqual(self.health_monitor['admin_state_up'],
- health_monitor['admin_state_up'])
+ for key, value in health_monitor.iteritems():
+ # 'status' should not be confirmed in api tests
+ if key != 'status':
+ self.assertEqual(self.health_monitor[key], value)
@test.attr(type='smoke')
def test_associate_disassociate_health_monitor_with_pool(self):
@@ -216,10 +345,26 @@
resp, body = (self.client.associate_health_monitor_with_pool
(self.health_monitor['id'], self.pool['id']))
self.assertEqual('201', resp['status'])
+ resp, body = self.client.show_health_monitor(
+ self.health_monitor['id'])
+ health_monitor = body['health_monitor']
+ resp, body = self.client.show_pool(self.pool['id'])
+ pool = body['pool']
+ self.assertIn(pool['id'],
+ [p['pool_id'] for p in health_monitor['pools']])
+ self.assertIn(health_monitor['id'], pool['health_monitors'])
# Verify that a health monitor can be disassociated from a pool
resp, body = (self.client.disassociate_health_monitor_with_pool
(self.health_monitor['id'], self.pool['id']))
self.assertEqual('204', resp['status'])
+ resp, body = self.client.show_pool(self.pool['id'])
+ pool = body['pool']
+ resp, body = self.client.show_health_monitor(
+ self.health_monitor['id'])
+ health_monitor = body['health_monitor']
+ self.assertNotIn(health_monitor['id'], pool['health_monitors'])
+ self.assertNotIn(pool['id'],
+ [p['pool_id'] for p in health_monitor['pools']])
@test.attr(type='smoke')
def test_get_lb_pool_stats(self):
diff --git a/tempest/api/network/test_networks.py b/tempest/api/network/test_networks.py
index 1155257..b9041ee 100644
--- a/tempest/api/network/test_networks.py
+++ b/tempest/api/network/test_networks.py
@@ -18,6 +18,7 @@
from tempest.api.network import base
from tempest.common.utils import data_utils
from tempest import config
+from tempest import exceptions
from tempest.test import attr
CONF = config.CONF
@@ -43,6 +44,8 @@
port update
network update
subnet update
+ delete a network also deletes its subnets
+ create a port with no IP address associated with it
All subnet tests are run once with ipv4 and once with ipv6.
@@ -67,17 +70,16 @@
cls.name = cls.network['name']
cls.subnet = cls.create_subnet(cls.network)
cls.cidr = cls.subnet['cidr']
- cls.port = cls.create_port(cls.network)
@attr(type='smoke')
def test_create_update_delete_network_subnet(self):
- # Creates a network
+ # Create a network
name = data_utils.rand_name('network-')
resp, body = self.client.create_network(name=name)
self.assertEqual('201', resp['status'])
network = body['network']
net_id = network['id']
- # Verification of network update
+ # Verify network update
new_name = "New_network"
resp, body = self.client.update_network(net_id, name=new_name)
self.assertEqual('200', resp['status'])
@@ -86,13 +88,12 @@
# Find a cidr that is not in use yet and create a subnet with it
subnet = self.create_subnet(network)
subnet_id = subnet['id']
- # Verification of subnet update
- new_subnet = "New_subnet"
- resp, body = self.client.update_subnet(subnet_id,
- name=new_subnet)
+ # Verify subnet update
+ new_name = "New_subnet"
+ resp, body = self.client.update_subnet(subnet_id, name=new_name)
self.assertEqual('200', resp['status'])
updated_subnet = body['subnet']
- self.assertEqual(updated_subnet['name'], new_subnet)
+ self.assertEqual(updated_subnet['name'], new_name)
# Delete subnet and network
resp, body = self.client.delete_subnet(subnet_id)
self.assertEqual('204', resp['status'])
@@ -103,191 +104,148 @@
@attr(type='smoke')
def test_show_network(self):
- # Verifies the details of a network
+ # Verify the details of a network
resp, body = self.client.show_network(self.network['id'])
self.assertEqual('200', resp['status'])
network = body['network']
- self.assertEqual(self.network['id'], network['id'])
- self.assertEqual(self.name, network['name'])
+ for key in ['id', 'name']:
+ self.assertEqual(network[key], self.network[key])
@attr(type='smoke')
def test_show_network_fields(self):
- # Verifies showing some fields of a network works
+ # Verify specific fields of a network
field_list = [('fields', 'id'), ('fields', 'name'), ]
resp, body = self.client.show_network(self.network['id'],
field_list=field_list)
self.assertEqual('200', resp['status'])
network = body['network']
- self.assertEqual(len(network), 2)
- self.assertEqual(self.network['id'], network['id'])
- self.assertEqual(self.name, network['name'])
+ self.assertEqual(len(network), len(field_list))
+ for label, field_name in field_list:
+ self.assertEqual(network[field_name], self.network[field_name])
@attr(type='smoke')
def test_list_networks(self):
# Verify the network exists in the list of all networks
resp, body = self.client.list_networks()
self.assertEqual('200', resp['status'])
- networks = body['networks']
- found = None
- for n in networks:
- if (n['id'] == self.network['id']):
- found = n['id']
- msg = "Network list doesn't contain created network"
- self.assertIsNotNone(found, msg)
+ networks = [network['id'] for network in body['networks']
+ if network['id'] == self.network['id']]
+ self.assertNotEmpty(networks, "Created network not found in the list")
@attr(type='smoke')
def test_list_networks_fields(self):
- # Verify listing some fields of the networks
+ # Verify specific fields of the networks
resp, body = self.client.list_networks(fields='id')
self.assertEqual('200', resp['status'])
networks = body['networks']
- found = None
- for n in networks:
- self.assertEqual(len(n), 1)
- self.assertIn('id', n)
- if (n['id'] == self.network['id']):
- found = n['id']
- self.assertIsNotNone(found,
- "Created network id not found in the list")
+ self.assertNotEmpty(networks, "Network list returned is empty")
+ for network in networks:
+ self.assertEqual(len(network), 1)
+ self.assertIn('id', network)
@attr(type='smoke')
def test_show_subnet(self):
- # Verifies the details of a subnet
+ # Verify the details of a subnet
resp, body = self.client.show_subnet(self.subnet['id'])
self.assertEqual('200', resp['status'])
subnet = body['subnet']
- self.assertEqual(self.subnet['id'], subnet['id'])
- self.assertEqual(self.cidr, subnet['cidr'])
+ self.assertNotEmpty(subnet, "Subnet returned has no fields")
+ for key in ['id', 'cidr']:
+ self.assertIn(key, subnet)
+ self.assertEqual(subnet[key], self.subnet[key])
@attr(type='smoke')
def test_show_subnet_fields(self):
- # Verifies showing some fields of a subnet works
+ # Verify specific fields of a subnet
field_list = [('fields', 'id'), ('fields', 'cidr'), ]
resp, body = self.client.show_subnet(self.subnet['id'],
field_list=field_list)
self.assertEqual('200', resp['status'])
subnet = body['subnet']
- self.assertEqual(len(subnet), 2)
- self.assertEqual(self.subnet['id'], subnet['id'])
- self.assertEqual(self.cidr, subnet['cidr'])
+ self.assertEqual(len(subnet), len(field_list))
+ for label, field_name in field_list:
+ self.assertEqual(subnet[field_name], self.subnet[field_name])
@attr(type='smoke')
def test_list_subnets(self):
# Verify the subnet exists in the list of all subnets
resp, body = self.client.list_subnets()
self.assertEqual('200', resp['status'])
- subnets = body['subnets']
- found = None
- for n in subnets:
- if (n['id'] == self.subnet['id']):
- found = n['id']
- msg = "Subnet list doesn't contain created subnet"
- self.assertIsNotNone(found, msg)
+ subnets = [subnet['id'] for subnet in body['subnets']
+ if subnet['id'] == self.subnet['id']]
+ self.assertNotEmpty(subnets, "Created subnet not found in the list")
@attr(type='smoke')
def test_list_subnets_fields(self):
- # Verify listing some fields of the subnets
+ # Verify specific fields of subnets
resp, body = self.client.list_subnets(fields='id')
self.assertEqual('200', resp['status'])
subnets = body['subnets']
- found = None
- for n in subnets:
- self.assertEqual(len(n), 1)
- self.assertIn('id', n)
- if (n['id'] == self.subnet['id']):
- found = n['id']
- self.assertIsNotNone(found,
- "Created subnet id not found in the list")
+ self.assertNotEmpty(subnets, "Subnet list returned is empty")
+ for subnet in subnets:
+ self.assertEqual(len(subnet), 1)
+ self.assertIn('id', subnet)
+
+ def _try_delete_network(self, net_id):
+ # delete network, if it exists
+ try:
+ self.client.delete_network(net_id)
+ # if network is not found, this means it was deleted in the test
+ except exceptions.NotFound:
+ pass
@attr(type='smoke')
- def test_create_update_delete_port(self):
- # Verify that successful port creation, update & deletion
- resp, body = self.client.create_port(
- network_id=self.network['id'])
+ def test_delete_network_with_subnet(self):
+ # Creates a network
+ name = data_utils.rand_name('network-')
+ resp, body = self.client.create_network(name=name)
self.assertEqual('201', resp['status'])
- port = body['port']
- self.assertTrue(port['admin_state_up'])
- # Verification of port update
- new_port = "New_Port"
- resp, body = self.client.update_port(
- port['id'],
- name=new_port,
- admin_state_up=False)
- self.assertEqual('200', resp['status'])
- updated_port = body['port']
- self.assertEqual(updated_port['name'], new_port)
- self.assertFalse(updated_port['admin_state_up'])
- # Verification of port delete
- resp, body = self.client.delete_port(port['id'])
+ network = body['network']
+ net_id = network['id']
+ self.addCleanup(self._try_delete_network, net_id)
+
+ # Find a cidr that is not in use yet and create a subnet with it
+ subnet = self.create_subnet(network)
+ subnet_id = subnet['id']
+
+ # Delete network while the subnet still exists
+ resp, body = self.client.delete_network(net_id)
self.assertEqual('204', resp['status'])
+ # Verify that the subnet got automatically deleted.
+ self.assertRaises(exceptions.NotFound, self.client.show_subnet,
+ subnet_id)
+
+ # Since create_subnet adds the subnet to the delete list, and it is
+ # is actually deleted here - this will create and issue, hence remove
+ # it from the list.
+ self.subnets.pop()
+
@attr(type='smoke')
- def test_show_port(self):
- # Verify the details of port
- resp, body = self.client.show_port(self.port['id'])
- self.assertEqual('200', resp['status'])
+ def test_create_port_with_no_ip(self):
+ # For this test create a new network - do not use any previously
+ # created networks.
+ name = data_utils.rand_name('network-nosubnet-')
+ resp, body = self.client.create_network(name=name)
+ self.assertEqual('201', resp['status'])
+ network = body['network']
+ net_id = network['id']
+ self.networks.append(network)
+
+ # Now create a port for this network - without creating any
+ # subnets for this network - this ensures no IP for the port
+ resp, body = self.client.create_port(network_id=net_id)
+ self.assertEqual('201', resp['status'])
port = body['port']
- self.assertEqual(self.port['id'], port['id'])
+ port_id = port['id']
+ self.addCleanup(self.client.delete_port, port_id)
- @attr(type='smoke')
- def test_show_port_fields(self):
- # Verifies showing fields of a port works
- field_list = [('fields', 'id'), ]
- resp, body = self.client.show_port(self.port['id'],
- field_list=field_list)
+ # Verify that the port does not have any IP address
+ resp, body = self.client.show_port(port_id)
self.assertEqual('200', resp['status'])
- port = body['port']
- self.assertEqual(len(port), 1)
- self.assertEqual(self.port['id'], port['id'])
-
- @attr(type='smoke')
- def test_list_ports(self):
- # Verify the port exists in the list of all ports
- resp, body = self.client.list_ports()
- self.assertEqual('200', resp['status'])
- ports_list = body['ports']
- found = None
- for n in ports_list:
- if (n['id'] == self.port['id']):
- found = n['id']
- self.assertIsNotNone(found, "Port list doesn't contain created port")
-
- @attr(type='smoke')
- def test_port_list_filter_by_router_id(self):
- # Create a router
- network = self.create_network()
- self.create_subnet(network)
- router = self.create_router(data_utils.rand_name('router-'))
- resp, port = self.client.create_port(
- network_id=network['id'])
- # Add router interface to port created above
- resp, interface = self.client.add_router_interface_with_port_id(
- router['id'], port['port']['id'])
- self.addCleanup(self.client.remove_router_interface_with_port_id,
- router['id'], port['port']['id'])
- # list ports filtered by router_id
- resp, port_list = self.client.list_ports(
- device_id=router['id'])
- self.assertEqual('200', resp['status'])
- # Verify if only corresponding port is listed and assert router_id
- self.assertEqual(len(port_list['ports']), 1)
- self.assertEqual(port['port']['id'], port_list['ports'][0]['id'])
- self.assertEqual(router['id'], port_list['ports'][0]['device_id'])
-
- @attr(type='smoke')
- def test_list_ports_fields(self):
- # Verify listing some fields of the ports
- resp, body = self.client.list_ports(fields='id')
- self.assertEqual('200', resp['status'])
- ports_list = body['ports']
- found = None
- for n in ports_list:
- self.assertEqual(len(n), 1)
- self.assertIn('id', n)
- if (n['id'] == self.port['id']):
- found = n['id']
- self.assertIsNotNone(found,
- "Created port id not found in the list")
+ port_resp = body['port']
+ self.assertEqual(port_id, port_resp['id'])
+ self.assertEqual(port_resp['fixed_ips'], [])
class NetworksTestXML(NetworksTestJSON):
@@ -328,9 +286,7 @@
self.assertEqual(204, resp.status)
# Asserting that the networks are not found in the list after deletion
resp, body = self.client.list_networks()
- networks_list = list()
- for network in body['networks']:
- networks_list.append(network['id'])
+ networks_list = [network['id'] for network in body['networks']]
for n in created_networks:
self.assertNotIn(n['id'], networks_list)
@@ -340,9 +296,7 @@
self.assertEqual(204, resp.status)
# Asserting that the subnets are not found in the list after deletion
resp, body = self.client.list_subnets()
- subnets_list = list()
- for subnet in body['subnets']:
- subnets_list.append(subnet['id'])
+ subnets_list = [subnet['id'] for subnet in body['subnets']]
for n in created_subnets:
self.assertNotIn(n['id'], subnets_list)
@@ -352,9 +306,7 @@
self.assertEqual(204, resp.status)
# Asserting that the ports are not found in the list after deletion
resp, body = self.client.list_ports()
- ports_list = list()
- for port in body['ports']:
- ports_list.append(port['id'])
+ ports_list = [port['id'] for port in body['ports']]
for n in created_ports:
self.assertNotIn(n['id'], ports_list)
@@ -369,9 +321,7 @@
self.addCleanup(self._delete_networks, created_networks)
# Asserting that the networks are found in the list after creation
resp, body = self.client.list_networks()
- networks_list = list()
- for network in body['networks']:
- networks_list.append(network['id'])
+ networks_list = [network['id'] for network in body['networks']]
for n in created_networks:
self.assertIsNotNone(n['id'])
self.assertIn(n['id'], networks_list)
@@ -381,14 +331,10 @@
# Creates 2 subnets in one request
cidr = netaddr.IPNetwork(CONF.network.tenant_network_cidr)
mask_bits = CONF.network.tenant_network_mask_bits
- cidrs = []
- for subnet_cidr in cidr.subnet(mask_bits):
- cidrs.append(subnet_cidr)
- names = []
+ cidrs = [subnet_cidr for subnet_cidr in cidr.subnet(mask_bits)]
networks = [self.network1['id'], self.network2['id']]
- for i in range(len(networks)):
- names.append(data_utils.rand_name('subnet-'))
- subnet_list = []
+ names = [data_utils.rand_name('subnet-') for i in range(len(networks))]
+ subnets_list = []
# TODO(raies): "for IPv6, version list [4, 6] will be used.
# and cidr for IPv6 will be of IPv6"
ip_version = [4, 4]
@@ -399,17 +345,15 @@
'name': names[i],
'ip_version': ip_version[i]
}
- subnet_list.append(p1)
- del subnet_list[1]['name']
- resp, body = self.client.create_bulk_subnet(subnet_list)
+ subnets_list.append(p1)
+ del subnets_list[1]['name']
+ resp, body = self.client.create_bulk_subnet(subnets_list)
created_subnets = body['subnets']
self.addCleanup(self._delete_subnets, created_subnets)
self.assertEqual('201', resp['status'])
# Asserting that the subnets are found in the list after creation
resp, body = self.client.list_subnets()
- subnets_list = list()
- for subnet in body['subnets']:
- subnets_list.append(subnet['id'])
+ subnets_list = [subnet['id'] for subnet in body['subnets']]
for n in created_subnets:
self.assertIsNotNone(n['id'])
self.assertIn(n['id'], subnets_list)
@@ -417,10 +361,8 @@
@attr(type='smoke')
def test_bulk_create_delete_port(self):
# Creates 2 ports in one request
- names = []
networks = [self.network1['id'], self.network2['id']]
- for i in range(len(networks)):
- names.append(data_utils.rand_name('port-'))
+ names = [data_utils.rand_name('port-') for i in range(len(networks))]
port_list = []
state = [True, False]
for i in range(len(names)):
@@ -437,9 +379,7 @@
self.assertEqual('201', resp['status'])
# Asserting that the ports are found in the list after creation
resp, body = self.client.list_ports()
- ports_list = list()
- for port in body['ports']:
- ports_list.append(port['id'])
+ ports_list = [port['id'] for port in body['ports']]
for n in created_ports:
self.assertIsNotNone(n['id'])
self.assertIn(n['id'], ports_list)
diff --git a/tempest/api/network/test_ports.py b/tempest/api/network/test_ports.py
new file mode 100644
index 0000000..fbb25a8
--- /dev/null
+++ b/tempest/api/network/test_ports.py
@@ -0,0 +1,249 @@
+# Copyright 2014 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 socket
+
+from tempest.api.network import base
+from tempest.common.utils import data_utils
+from tempest import config
+from tempest import test
+
+CONF = config.CONF
+
+
+class PortsTestJSON(base.BaseNetworkTest):
+ _interface = 'json'
+
+ @classmethod
+ def setUpClass(cls):
+ super(PortsTestJSON, cls).setUpClass()
+ cls.network = cls.create_network()
+ cls.port = cls.create_port(cls.network)
+
+ def _delete_port(self, port_id):
+ resp, body = self.client.delete_port(port_id)
+ self.assertEqual('204', resp['status'])
+ resp, body = self.client.list_ports()
+ self.assertEqual('200', resp['status'])
+ ports_list = body['ports']
+ self.assertFalse(port_id in [n['id'] for n in ports_list])
+
+ @test.attr(type='smoke')
+ def test_create_update_delete_port(self):
+ # Verify port creation
+ resp, body = self.client.create_port(network_id=self.network['id'])
+ self.assertEqual('201', resp['status'])
+ port = body['port']
+ self.assertTrue(port['admin_state_up'])
+ # Verify port update
+ new_name = "New_Port"
+ resp, body = self.client.update_port(
+ port['id'],
+ name=new_name,
+ admin_state_up=False)
+ self.assertEqual('200', resp['status'])
+ updated_port = body['port']
+ self.assertEqual(updated_port['name'], new_name)
+ self.assertFalse(updated_port['admin_state_up'])
+ # Verify port deletion
+ resp, body = self.client.delete_port(port['id'])
+ self.assertEqual('204', resp['status'])
+
+ @test.attr(type='smoke')
+ def test_show_port(self):
+ # Verify the details of port
+ resp, body = self.client.show_port(self.port['id'])
+ self.assertEqual('200', resp['status'])
+ port = body['port']
+ self.assertIn('id', port)
+ self.assertEqual(port['id'], self.port['id'])
+ self.assertEqual(self.port['admin_state_up'], port['admin_state_up'])
+ self.assertEqual(self.port['device_id'], port['device_id'])
+ self.assertEqual(self.port['device_owner'], port['device_owner'])
+ self.assertEqual(self.port['mac_address'], port['mac_address'])
+ self.assertEqual(self.port['name'], port['name'])
+ self.assertEqual(self.port['security_groups'],
+ port['security_groups'])
+ self.assertEqual(self.port['network_id'], port['network_id'])
+ self.assertEqual(self.port['security_groups'],
+ port['security_groups'])
+
+ @test.attr(type='smoke')
+ def test_show_port_fields(self):
+ # Verify specific fields of a port
+ field_list = [('fields', 'id'), ]
+ resp, body = self.client.show_port(self.port['id'],
+ field_list=field_list)
+ self.assertEqual('200', resp['status'])
+ port = body['port']
+ self.assertEqual(len(port), len(field_list))
+ for label, field_name in field_list:
+ self.assertEqual(port[field_name], self.port[field_name])
+
+ @test.attr(type='smoke')
+ def test_list_ports(self):
+ # Verify the port exists in the list of all ports
+ resp, body = self.client.list_ports()
+ self.assertEqual('200', resp['status'])
+ ports = [port['id'] for port in body['ports']
+ if port['id'] == self.port['id']]
+ self.assertNotEmpty(ports, "Created port not found in the list")
+
+ @test.attr(type='smoke')
+ def test_port_list_filter_by_router_id(self):
+ # Create a router
+ network = self.create_network()
+ self.create_subnet(network)
+ router = self.create_router(data_utils.rand_name('router-'))
+ resp, port = self.client.create_port(network_id=network['id'])
+ # Add router interface to port created above
+ resp, interface = self.client.add_router_interface_with_port_id(
+ router['id'], port['port']['id'])
+ self.addCleanup(self.client.remove_router_interface_with_port_id,
+ router['id'], port['port']['id'])
+ # List ports filtered by router_id
+ resp, port_list = self.client.list_ports(
+ device_id=router['id'])
+ self.assertEqual('200', resp['status'])
+ ports = port_list['ports']
+ self.assertEqual(len(ports), 1)
+ self.assertEqual(ports[0]['id'], port['port']['id'])
+ self.assertEqual(ports[0]['device_id'], router['id'])
+
+ @test.attr(type='smoke')
+ def test_list_ports_fields(self):
+ # Verify specific fields of ports
+ resp, body = self.client.list_ports(fields='id')
+ self.assertEqual('200', resp['status'])
+ ports = body['ports']
+ self.assertNotEmpty(ports, "Port list returned is empty")
+ # Asserting the fields returned are correct
+ for port in ports:
+ self.assertEqual(len(port), 1)
+ self.assertIn('id', port)
+
+
+class PortsTestXML(PortsTestJSON):
+ _interface = 'xml'
+
+
+class PortsAdminExtendedAttrsTestJSON(base.BaseAdminNetworkTest):
+ _interface = 'json'
+
+ @classmethod
+ def setUpClass(cls):
+ super(PortsAdminExtendedAttrsTestJSON, cls).setUpClass()
+ cls.identity_client = cls._get_identity_admin_client()
+ cls.tenant = cls.identity_client.get_tenant_by_name(
+ CONF.identity.tenant_name)
+ cls.network = cls.create_network()
+ cls.host_id = socket.gethostname()
+
+ @test.attr(type='smoke')
+ def test_create_port_binding_ext_attr(self):
+ post_body = {"network_id": self.network['id'],
+ "binding:host_id": self.host_id}
+ resp, body = self.admin_client.create_port(**post_body)
+ self.assertEqual('201', resp['status'])
+ port = body['port']
+ self.addCleanup(self.admin_client.delete_port, port['id'])
+ host_id = port['binding:host_id']
+ self.assertIsNotNone(host_id)
+ self.assertEqual(self.host_id, host_id)
+
+ @test.attr(type='smoke')
+ def test_update_port_binding_ext_attr(self):
+ post_body = {"network_id": self.network['id']}
+ resp, body = self.admin_client.create_port(**post_body)
+ self.assertEqual('201', resp['status'])
+ port = body['port']
+ self.addCleanup(self.admin_client.delete_port, port['id'])
+ update_body = {"binding:host_id": self.host_id}
+ resp, body = self.admin_client.update_port(port['id'], **update_body)
+ self.assertEqual('200', resp['status'])
+ updated_port = body['port']
+ host_id = updated_port['binding:host_id']
+ self.assertIsNotNone(host_id)
+ self.assertEqual(self.host_id, host_id)
+
+ @test.attr(type='smoke')
+ def test_list_ports_binding_ext_attr(self):
+ resp, body = self.admin_client.list_ports(
+ **{'tenant_id': self.tenant['id']})
+ self.assertEqual('200', resp['status'])
+ ports_list = body['ports']
+ for port in ports_list:
+ vif_type = port['binding:vif_type']
+ self.assertIsNotNone(vif_type)
+ vif_details = port['binding:vif_details']['port_filter']
+ self.assertIsNotNone(vif_details)
+
+ @test.attr(type='smoke')
+ def test_show_port_binding_ext_attr(self):
+ resp, body = self.admin_client.create_port(
+ network_id=self.network['id'])
+ self.assertEqual('201', resp['status'])
+ port = body['port']
+ self.addCleanup(self.admin_client.delete_port, port['id'])
+ resp, body = self.admin_client.show_port(port['id'])
+ self.assertEqual('200', resp['status'])
+ show_port = body['port']
+ self.assertEqual(port['binding:host_id'],
+ show_port['binding:host_id'])
+ self.assertEqual(port['binding:vif_type'],
+ show_port['binding:vif_type'])
+ self.assertEqual(port['binding:vif_details'],
+ show_port['binding:vif_details'])
+
+
+class PortsAdminExtendedAttrsTestXML(PortsAdminExtendedAttrsTestJSON):
+ _interface = 'xml'
+
+
+class PortsIpV6TestJSON(PortsTestJSON):
+ _ip_version = 6
+ _tenant_network_cidr = CONF.network.tenant_network_v6_cidr
+ _tenant_network_mask_bits = CONF.network.tenant_network_v6_mask_bits
+
+ @classmethod
+ def setUpClass(cls):
+ super(PortsIpV6TestJSON, cls).setUpClass()
+ if not CONF.network_feature_enabled.ipv6:
+ cls.tearDownClass()
+ skip_msg = "IPv6 Tests are disabled."
+ raise cls.skipException(skip_msg)
+
+
+class PortsIpV6TestXML(PortsIpV6TestJSON):
+ _interface = 'xml'
+
+
+class PortsAdminExtendedAttrsIpV6TestJSON(PortsAdminExtendedAttrsTestJSON):
+ _ip_version = 6
+ _tenant_network_cidr = CONF.network.tenant_network_v6_cidr
+ _tenant_network_mask_bits = CONF.network.tenant_network_v6_mask_bits
+
+ @classmethod
+ def setUpClass(cls):
+ super(PortsAdminExtendedAttrsIpV6TestJSON, cls).setUpClass()
+ if not CONF.network_feature_enabled.ipv6:
+ cls.tearDownClass()
+ skip_msg = "IPv6 Tests are disabled."
+ raise cls.skipException(skip_msg)
+
+
+class PortsAdminExtendedAttrsIpV6TestXML(
+ PortsAdminExtendedAttrsIpV6TestJSON):
+ _interface = 'xml'
diff --git a/tempest/api/object_storage/test_account_quotas.py b/tempest/api/object_storage/test_account_quotas.py
index b14adc0..a3098a5 100644
--- a/tempest/api/object_storage/test_account_quotas.py
+++ b/tempest/api/object_storage/test_account_quotas.py
@@ -68,7 +68,7 @@
# Retrieve a ResellerAdmin auth data and use it to set a quota
# on the client's account
cls.reselleradmin_auth_data = \
- cls.os_reselleradmin.get_auth_provider().auth_data
+ cls.os_reselleradmin.auth_provider.auth_data
def setUp(self):
super(AccountQuotasTest, self).setUp()
diff --git a/tempest/api/object_storage/test_account_quotas_negative.py b/tempest/api/object_storage/test_account_quotas_negative.py
index 402cd90..7648ea1 100644
--- a/tempest/api/object_storage/test_account_quotas_negative.py
+++ b/tempest/api/object_storage/test_account_quotas_negative.py
@@ -68,7 +68,7 @@
# Retrieve a ResellerAdmin auth data and use it to set a quota
# on the client's account
cls.reselleradmin_auth_data = \
- cls.os_reselleradmin.get_auth_provider().auth_data
+ cls.os_reselleradmin.auth_provider.auth_data
def setUp(self):
super(AccountQuotasNegativeTest, self).setUp()
diff --git a/tempest/api/object_storage/test_account_services_negative.py b/tempest/api/object_storage/test_account_services_negative.py
index ea93aa3..71eaab5 100644
--- a/tempest/api/object_storage/test_account_services_negative.py
+++ b/tempest/api/object_storage/test_account_services_negative.py
@@ -31,7 +31,7 @@
test_os = clients.Manager(self.data.test_user,
self.data.test_password,
self.data.test_tenant)
- test_auth_provider = test_os.get_auth_provider()
+ test_auth_provider = test_os.auth_provider
# Get auth for the test user
test_auth_provider.auth_data
diff --git a/tempest/api/object_storage/test_container_acl.py b/tempest/api/object_storage/test_container_acl.py
index 085ef51..c865ee1 100644
--- a/tempest/api/object_storage/test_container_acl.py
+++ b/tempest/api/object_storage/test_container_acl.py
@@ -27,7 +27,7 @@
test_os = clients.Manager(cls.data.test_user,
cls.data.test_password,
cls.data.test_tenant)
- cls.test_auth_data = test_os.get_auth_provider().auth_data
+ cls.test_auth_data = test_os.auth_provider.auth_data
@classmethod
def tearDownClass(cls):
diff --git a/tempest/api/object_storage/test_container_acl_negative.py b/tempest/api/object_storage/test_container_acl_negative.py
index a5a0950..547bf87 100644
--- a/tempest/api/object_storage/test_container_acl_negative.py
+++ b/tempest/api/object_storage/test_container_acl_negative.py
@@ -29,7 +29,7 @@
test_os = clients.Manager(cls.data.test_user,
cls.data.test_password,
cls.data.test_tenant)
- cls.test_auth_data = test_os.get_auth_provider().auth_data
+ cls.test_auth_data = test_os.auth_provider.auth_data
@classmethod
def tearDownClass(cls):
diff --git a/tempest/api/orchestration/stacks/test_neutron_resources.py b/tempest/api/orchestration/stacks/test_neutron_resources.py
index 18ba37b..dee26b1 100644
--- a/tempest/api/orchestration/stacks/test_neutron_resources.py
+++ b/tempest/api/orchestration/stacks/test_neutron_resources.py
@@ -18,7 +18,7 @@
from tempest.common.utils import data_utils
from tempest import config
from tempest import exceptions
-from tempest.test import attr
+from tempest import test
CONF = config.CONF
@@ -100,6 +100,7 @@
"""
@classmethod
+ @test.safe_setup
def setUpClass(cls):
super(NeutronResourcesTestJSON, cls).setUpClass()
if not CONF.orchestration.image_ref:
@@ -155,7 +156,7 @@
'network:router_interface', ports)
return router_ports[0]['device_id']
- @attr(type='slow')
+ @test.attr(type='slow')
def test_created_resources(self):
"""Verifies created neutron resources."""
resources = [('Network', 'OS::Neutron::Net'),
@@ -169,7 +170,7 @@
self.assertEqual(resource_type, resource['resource_type'])
self.assertEqual('CREATE_COMPLETE', resource['resource_status'])
- @attr(type='slow')
+ @test.attr(type='slow')
def test_created_network(self):
"""Verifies created network."""
network_id = self.test_resources.get('Network')['physical_resource_id']
@@ -180,7 +181,7 @@
self.assertEqual(network_id, network['id'])
self.assertEqual('NewNetwork', network['name'])
- @attr(type='slow')
+ @test.attr(type='slow')
def test_created_subnet(self):
"""Verifies created subnet."""
subnet_id = self.test_resources.get('Subnet')['physical_resource_id']
@@ -197,7 +198,7 @@
self.assertEqual(4, subnet['ip_version'])
self.assertEqual('10.0.3.0/24', subnet['cidr'])
- @attr(type='slow')
+ @test.attr(type='slow')
def test_created_router(self):
"""Verifies created router."""
router_id = self.test_resources.get('Router')['physical_resource_id']
@@ -211,7 +212,7 @@
router['external_gateway_info']['enable_snat'])
self.assertEqual(False, router['admin_state_up'])
- @attr(type='slow')
+ @test.attr(type='slow')
def test_created_router_interface(self):
"""Verifies created router interface."""
network_id = self.test_resources.get('Network')['physical_resource_id']
@@ -232,7 +233,7 @@
router_interface_ip = subnet_fixed_ips[0]['ip_address']
self.assertEqual('10.0.3.1', router_interface_ip)
- @attr(type='slow')
+ @test.attr(type='slow')
def test_created_server(self):
"""Verifies created sever."""
server_id = self.test_resources.get('Server')['physical_resource_id']
diff --git a/tempest/api/orchestration/stacks/test_nova_keypair_resources.py b/tempest/api/orchestration/stacks/test_nova_keypair_resources.py
new file mode 100644
index 0000000..9d3bf13
--- /dev/null
+++ b/tempest/api/orchestration/stacks/test_nova_keypair_resources.py
@@ -0,0 +1,181 @@
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+
+import logging
+
+from tempest.api.orchestration import base
+from tempest.common.utils import data_utils
+from tempest.test import attr
+
+
+LOG = logging.getLogger(__name__)
+
+
+class NovaKeyPairResourcesYAMLTest(base.BaseOrchestrationTest):
+ _interface = 'json'
+ template = """
+heat_template_version: 2013-05-23
+
+description: >
+ Template which creates two key pairs.
+
+parameters:
+ KeyPairName1:
+ type: string
+ default: testkey
+
+ KeyPairName2:
+ type: string
+ default: testkey2
+
+resources:
+ KeyPairSavePrivate:
+ type: OS::Nova::KeyPair
+ properties:
+ name: { get_param: KeyPairName1 }
+ save_private_key: true
+
+ KeyPairDontSavePrivate:
+ type: OS::Nova::KeyPair
+ properties:
+ name: { get_param: KeyPairName2 }
+ save_private_key: false
+
+outputs:
+ KeyPair_PublicKey:
+ description: Public Key of generated keypair
+ value: { get_attr: [KeyPairSavePrivate, public_key] }
+
+ KeyPair_PrivateKey:
+ description: Private Key of generated keypair
+ value: { get_attr: [KeyPairSavePrivate, private_key] }
+
+ KeyPairDontSavePrivate_PublicKey:
+ description: Public Key of generated keypair
+ value: { get_attr: [KeyPairDontSavePrivate, public_key] }
+
+ KeyPairDontSavePrivate_PrivateKey:
+ description: Private Key of generated keypair
+ value: { get_attr: [KeyPairDontSavePrivate, private_key] }
+"""
+
+ @classmethod
+ def setUpClass(cls):
+ super(NovaKeyPairResourcesYAMLTest, cls).setUpClass()
+ cls.client = cls.orchestration_client
+ cls.stack_name = data_utils.rand_name('heat')
+
+ # create the stack, avoid any duplicated key.
+ cls.stack_identifier = cls.create_stack(
+ cls.stack_name,
+ cls.template,
+ parameters={
+ 'KeyPairName1': cls.stack_name + '_1',
+ 'KeyPairName2': cls.stack_name + '_2'
+ })
+
+ cls.stack_id = cls.stack_identifier.split('/')[1]
+ cls.client.wait_for_stack_status(cls.stack_id, 'CREATE_COMPLETE')
+ _, resources = cls.client.list_resources(cls.stack_identifier)
+ cls.test_resources = {}
+ for resource in resources:
+ cls.test_resources[resource['logical_resource_id']] = resource
+
+ @attr(type='slow')
+ def test_created_resources(self):
+ """Verifies created keypair resource."""
+ resources = [('KeyPairSavePrivate', 'OS::Nova::KeyPair'),
+ ('KeyPairDontSavePrivate', 'OS::Nova::KeyPair')]
+
+ for resource_name, resource_type in resources:
+ resource = self.test_resources.get(resource_name, None)
+ self.assertIsInstance(resource, dict)
+ self.assertEqual(resource_name, resource['logical_resource_id'])
+ self.assertEqual(resource_type, resource['resource_type'])
+ self.assertEqual('CREATE_COMPLETE', resource['resource_status'])
+
+ @attr(type='slow')
+ def test_stack_keypairs_output(self):
+ resp, stack = self.client.get_stack(self.stack_name)
+ self.assertEqual('200', resp['status'])
+ self.assertIsInstance(stack, dict)
+
+ output_map = {}
+ for outputs in stack['outputs']:
+ output_map[outputs['output_key']] = outputs['output_value']
+ #Test that first key generated public and private keys
+ self.assertTrue('KeyPair_PublicKey' in output_map)
+ self.assertTrue("Generated by" in output_map['KeyPair_PublicKey'])
+ self.assertTrue('KeyPair_PrivateKey' in output_map)
+ self.assertTrue('-----BEGIN' in output_map['KeyPair_PrivateKey'])
+ #Test that second key generated public key, and private key is not
+ #in the output due to save_private_key = false
+ self.assertTrue('KeyPairDontSavePrivate_PublicKey' in output_map)
+ self.assertTrue('Generated by' in
+ output_map['KeyPairDontSavePrivate_PublicKey'])
+ self.assertTrue(u'KeyPairDontSavePrivate_PrivateKey' in output_map)
+ private_key = output_map['KeyPairDontSavePrivate_PrivateKey']
+ self.assertTrue(len(private_key) == 0)
+
+
+class NovaKeyPairResourcesAWSTest(NovaKeyPairResourcesYAMLTest):
+ template = """
+{
+ "AWSTemplateFormatVersion" : "2010-09-09",
+ "Description" : "Template which create two key pairs.",
+ "Parameters" : {
+ "KeyPairName1": {
+ "Type": "String",
+ "Default": "testkey1"
+ },
+ "KeyPairName2": {
+ "Type": "String",
+ "Default": "testkey2"
+ }
+ },
+ "Resources" : {
+ "KeyPairSavePrivate": {
+ "Type": "OS::Nova::KeyPair",
+ "Properties": {
+ "name" : { "Ref" : "KeyPairName1" },
+ "save_private_key": true
+ }
+ },
+ "KeyPairDontSavePrivate": {
+ "Type": "OS::Nova::KeyPair",
+ "Properties": {
+ "name" : { "Ref" : "KeyPairName2" },
+ "save_private_key": false
+ }
+ }
+ },
+ "Outputs": {
+ "KeyPair_PublicKey": {
+ "Description": "Public Key of generated keypair.",
+ "Value": { "Fn::GetAtt" : ["KeyPairSavePrivate", "public_key"] }
+ },
+ "KeyPair_PrivateKey": {
+ "Description": "Private Key of generated keypair.",
+ "Value": { "Fn::GetAtt" : ["KeyPairSavePrivate", "private_key"] }
+ },
+ "KeyPairDontSavePrivate_PublicKey": {
+ "Description": "Public Key of generated keypair.",
+ "Value": { "Fn::GetAtt" : ["KeyPairDontSavePrivate", "public_key"] }
+ },
+ "KeyPairDontSavePrivate_PrivateKey": {
+ "Description": "Private Key of generated keypair.",
+ "Value": { "Fn::GetAtt" : ["KeyPairDontSavePrivate", "private_key"] }
+ }
+ }
+}
+"""
diff --git a/tempest/api/orchestration/stacks/test_server_cfn_init.py b/tempest/api/orchestration/stacks/test_server_cfn_init.py
index 7e8bc2d..a6f74b6 100644
--- a/tempest/api/orchestration/stacks/test_server_cfn_init.py
+++ b/tempest/api/orchestration/stacks/test_server_cfn_init.py
@@ -17,6 +17,7 @@
from tempest.common.utils import data_utils
from tempest.common.utils.linux import remote_client
from tempest import config
+from tempest import exceptions
from tempest.openstack.common import log as logging
from tempest import test
@@ -66,18 +67,13 @@
content: smoke test complete
/etc/cfn/cfn-credentials:
content:
- Fn::Join:
- - ''
- - - AWSAccessKeyId=
- - {Ref: SmokeKeys}
- - '
-
- '
- - AWSSecretKey=
- - Fn::GetAtt: [SmokeKeys, SecretAccessKey]
- - '
-
- '
+ Fn::Replace:
+ - SmokeKeys: {Ref: SmokeKeys}
+ SecretAccessKey:
+ 'Fn::GetAtt': [SmokeKeys, SecretAccessKey]
+ - |
+ AWSAccessKeyId=SmokeKeys
+ AWSSecretKey=SecretAccessKey
mode: '000400'
owner: root
group: root
@@ -90,19 +86,13 @@
networks:
- uuid: {Ref: network}
user_data:
- Fn::Base64:
- Fn::Join:
- - ''
- - - |-
- #!/bin/bash -v
- /opt/aws/bin/cfn-init
- - |-
- || error_exit ''Failed to run cfn-init''
- /opt/aws/bin/cfn-signal -e 0 --data "`cat /tmp/smoke-status`" '
- - {Ref: WaitHandle}
- - '''
-
- '
+ Fn::Replace:
+ - WaitHandle: {Ref: WaitHandle}
+ - |
+ #!/bin/bash -v
+ /opt/aws/bin/cfn-init
+ /opt/aws/bin/cfn-signal -e 0 --data "`cat /tmp/smoke-status`" \
+ "WaitHandle"
WaitHandle:
Type: AWS::CloudFormation::WaitConditionHandle
WaitCondition:
@@ -123,6 +113,7 @@
"""
@classmethod
+ @test.safe_setup
def setUpClass(cls):
super(ServerCfnInitTestJSON, cls).setUpClass()
if not CONF.orchestration.image_ref:
@@ -172,9 +163,32 @@
linux_client.validate_authentication()
@test.attr(type='slow')
- def test_stack_wait_condition_data(self):
-
+ def test_all_resources_created(self):
sid = self.stack_identifier
+ self.client.wait_for_resource_status(
+ sid, 'WaitHandle', 'CREATE_COMPLETE')
+ self.client.wait_for_resource_status(
+ sid, 'SmokeSecurityGroup', 'CREATE_COMPLETE')
+ self.client.wait_for_resource_status(
+ sid, 'SmokeKeys', 'CREATE_COMPLETE')
+ self.client.wait_for_resource_status(
+ sid, 'CfnUser', 'CREATE_COMPLETE')
+ self.client.wait_for_resource_status(
+ sid, 'SmokeServer', 'CREATE_COMPLETE')
+ try:
+ self.client.wait_for_resource_status(
+ sid, 'WaitCondition', 'CREATE_COMPLETE')
+ except exceptions.TimeoutException as e:
+ # attempt to log the server console to help with debugging
+ # the cause of the server not signalling the waitcondition
+ # to heat.
+ resp, body = self.client.get_resource(sid, 'SmokeServer')
+ server_id = body['physical_resource_id']
+ LOG.debug('Console output for %s', server_id)
+ resp, output = self.servers_client.get_console_output(
+ server_id, None)
+ LOG.debug(output)
+ raise e
# wait for create to complete.
self.client.wait_for_stack_status(sid, 'CREATE_COMPLETE')
diff --git a/tempest/api/orchestration/stacks/test_swift_resources.py b/tempest/api/orchestration/stacks/test_swift_resources.py
index 713cfd4..fcf357a 100644
--- a/tempest/api/orchestration/stacks/test_swift_resources.py
+++ b/tempest/api/orchestration/stacks/test_swift_resources.py
@@ -18,6 +18,7 @@
from tempest import clients
from tempest.common.utils import data_utils
from tempest import config
+from tempest import test
CONF = config.CONF
@@ -53,6 +54,7 @@
"""
@classmethod
+ @test.safe_setup
def setUpClass(cls):
super(SwiftResourcesTestJSON, cls).setUpClass()
cls.client = cls.orchestration_client
diff --git a/tempest/api/orchestration/stacks/test_templates.py b/tempest/api/orchestration/stacks/test_templates.py
index 2da819d..2dc29fc 100644
--- a/tempest/api/orchestration/stacks/test_templates.py
+++ b/tempest/api/orchestration/stacks/test_templates.py
@@ -12,7 +12,7 @@
from tempest.api.orchestration import base
from tempest.common.utils import data_utils
-from tempest.test import attr
+from tempest import test
class TemplateYAMLTestJSON(base.BaseOrchestrationTest):
@@ -30,6 +30,7 @@
invalid_template_url = 'http://www.example.com/template.yaml'
@classmethod
+ @test.safe_setup
def setUpClass(cls):
super(TemplateYAMLTestJSON, cls).setUpClass()
cls.client = cls.orchestration_client
@@ -40,13 +41,13 @@
cls.stack_id = cls.stack_identifier.split('/')[1]
cls.parameters = {}
- @attr(type='gate')
+ @test.attr(type='gate')
def test_show_template(self):
"""Getting template used to create the stack."""
resp, template = self.client.show_template(self.stack_identifier)
self.assertEqual('200', resp['status'])
- @attr(type='gate')
+ @test.attr(type='gate')
def test_validate_template(self):
"""Validating template passing it content."""
resp, parameters = self.client.validate_template(self.template,
diff --git a/tempest/api/queuing/__init__.py b/tempest/api/queuing/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/api/queuing/__init__.py
diff --git a/tempest/api/queuing/base.py b/tempest/api/queuing/base.py
new file mode 100644
index 0000000..6c22719
--- /dev/null
+++ b/tempest/api/queuing/base.py
@@ -0,0 +1,55 @@
+# Copyright (c) 2014 Rackspace, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from tempest import config
+from tempest.openstack.common import log as logging
+from tempest import test
+
+CONF = config.CONF
+
+LOG = logging.getLogger(__name__)
+
+
+class BaseQueuingTest(test.BaseTestCase):
+
+ """
+ Base class for the Queuing tests that use the Tempest Marconi REST client
+
+ It is assumed that the following option is defined in the
+ [service_available] section of etc/tempest.conf
+
+ queuing as True
+ """
+
+ @classmethod
+ def setUpClass(cls):
+ super(BaseQueuingTest, cls).setUpClass()
+ if not CONF.service_available.marconi:
+ raise cls.skipException("Marconi support is required")
+ os = cls.get_client_manager()
+ cls.queuing_cfg = CONF.queuing
+ cls.client = os.queuing_client
+
+ @classmethod
+ def create_queue(cls, queue_name):
+ """Wrapper utility that returns a test queue."""
+ resp, body = cls.client.create_queue(queue_name)
+ return resp, body
+
+ @classmethod
+ def delete_queue(cls, queue_name):
+ """Wrapper utility that returns a test queue."""
+ resp, body = cls.client.delete_queue(queue_name)
+ return resp, body
diff --git a/tempest/api/queuing/test_queues.py b/tempest/api/queuing/test_queues.py
new file mode 100644
index 0000000..4d03f7e
--- /dev/null
+++ b/tempest/api/queuing/test_queues.py
@@ -0,0 +1,60 @@
+# Copyright (c) 2014 Rackspace, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import logging
+
+from tempest.api.queuing import base
+from tempest.common.utils import data_utils
+from tempest import test
+
+
+LOG = logging.getLogger(__name__)
+
+
+class TestQueues(base.BaseQueuingTest):
+
+ @test.attr(type='smoke')
+ def test_create_queue(self):
+ # Create Queue
+ queue_name = data_utils.rand_name('test-')
+ resp, body = self.create_queue(queue_name)
+
+ self.addCleanup(self.client.delete_queue, queue_name)
+
+ self.assertEqual('201', resp['status'])
+ self.assertEqual('', body)
+
+
+class TestManageQueue(base.BaseQueuingTest):
+ _interface = 'json'
+
+ @classmethod
+ def setUpClass(cls):
+ super(TestManageQueue, cls).setUpClass()
+ cls.queue_name = data_utils.rand_name('Queues-Test')
+ # Create Queue
+ cls.client.create_queue(cls.queue_name)
+
+ @test.attr(type='smoke')
+ def test_delete_queue(self):
+ # Delete Queue
+ resp, body = self.delete_queue(self.queue_name)
+ self.assertEqual('204', resp['status'])
+ self.assertEqual('', body)
+
+ @classmethod
+ def tearDownClass(cls):
+ cls.client.delete_queue(cls.queue_name)
+ super(TestManageQueue, cls).tearDownClass()
diff --git a/tempest/api/volume/admin/test_snapshots_actions.py b/tempest/api/volume/admin/test_snapshots_actions.py
index e140ad0..594c703 100644
--- a/tempest/api/volume/admin/test_snapshots_actions.py
+++ b/tempest/api/volume/admin/test_snapshots_actions.py
@@ -22,6 +22,7 @@
_interface = "json"
@classmethod
+ @test.safe_setup
def setUpClass(cls):
super(SnapshotsActionsTest, cls).setUpClass()
cls.client = cls.snapshots_client
diff --git a/tempest/api/volume/admin/test_volume_quotas.py b/tempest/api/volume/admin/test_volume_quotas.py
new file mode 100644
index 0000000..2949d56
--- /dev/null
+++ b/tempest/api/volume/admin/test_volume_quotas.py
@@ -0,0 +1,105 @@
+# Copyright (C) 2014 eNovance SAS <licensing@enovance.com>
+#
+# Author: Sylvain Baubeau <sylvain.baubeau@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.volume import base
+from tempest import test
+
+QUOTA_KEYS = ['gigabytes', 'snapshots', 'volumes']
+QUOTA_USAGE_KEYS = ['reserved', 'limit', 'in_use']
+
+
+class VolumeQuotasAdminTestJSON(base.BaseVolumeV1AdminTest):
+ _interface = "json"
+ force_tenant_isolation = True
+
+ @classmethod
+ def setUpClass(cls):
+ super(VolumeQuotasAdminTestJSON, cls).setUpClass()
+ cls.admin_volume_client = cls.os_adm.volumes_client
+ cls.demo_tenant_id = cls.isolated_creds.get_primary_user().get(
+ 'tenantId')
+
+ @test.attr(type='gate')
+ def test_list_quotas(self):
+ resp, quotas = self.quotas_client.get_quota_set(self.demo_tenant_id)
+ self.assertEqual(200, resp.status)
+ for key in QUOTA_KEYS:
+ self.assertIn(key, quotas)
+
+ @test.attr(type='gate')
+ def test_list_default_quotas(self):
+ resp, quotas = self.quotas_client.get_default_quota_set(
+ self.demo_tenant_id)
+ self.assertEqual(200, resp.status)
+ for key in QUOTA_KEYS:
+ self.assertIn(key, quotas)
+
+ @test.attr(type='gate')
+ def test_update_all_quota_resources_for_tenant(self):
+ # Admin can update all the resource quota limits for a tenant
+ resp, default_quota_set = self.quotas_client.get_default_quota_set(
+ self.demo_tenant_id)
+ new_quota_set = {'gigabytes': 1009,
+ 'volumes': 11,
+ 'snapshots': 11}
+
+ # Update limits for all quota resources
+ resp, quota_set = self.quotas_client.update_quota_set(
+ self.demo_tenant_id,
+ **new_quota_set)
+
+ cleanup_quota_set = dict(
+ (k, v) for k, v in default_quota_set.iteritems()
+ if k in QUOTA_KEYS)
+ self.addCleanup(self.quotas_client.update_quota_set,
+ self.demo_tenant_id, **cleanup_quota_set)
+ self.assertEqual(200, resp.status)
+ # test that the specific values we set are actually in
+ # the final result. There is nothing here that ensures there
+ # would be no other values in there.
+ self.assertDictContainsSubset(new_quota_set, quota_set)
+
+ @test.attr(type='gate')
+ def test_show_quota_usage(self):
+ resp, quota_usage = self.quotas_client.get_quota_usage(self.adm_tenant)
+ self.assertEqual(200, resp.status)
+ for key in QUOTA_KEYS:
+ self.assertIn(key, quota_usage)
+ for usage_key in QUOTA_USAGE_KEYS:
+ self.assertIn(usage_key, quota_usage[key])
+
+ @test.attr(type='gate')
+ def test_quota_usage(self):
+ resp, quota_usage = self.quotas_client.get_quota_usage(
+ self.demo_tenant_id)
+
+ volume = self.create_volume(size=1)
+ self.addCleanup(self.admin_volume_client.delete_volume,
+ volume['id'])
+
+ resp, new_quota_usage = self.quotas_client.get_quota_usage(
+ self.demo_tenant_id)
+
+ self.assertEqual(200, resp.status)
+ self.assertEqual(quota_usage['volumes']['in_use'] + 1,
+ new_quota_usage['volumes']['in_use'])
+
+ self.assertEqual(quota_usage['gigabytes']['in_use'] + 1,
+ new_quota_usage['gigabytes']['in_use'])
+
+
+class VolumeQuotasAdminTestXML(VolumeQuotasAdminTestJSON):
+ _interface = "xml"
diff --git a/tempest/api/volume/admin/test_volume_types.py b/tempest/api/volume/admin/test_volume_types.py
index 8183999..ee1d09a 100644
--- a/tempest/api/volume/admin/test_volume_types.py
+++ b/tempest/api/volume/admin/test_volume_types.py
@@ -116,3 +116,35 @@
self.assertEqual(extra_specs, fetched_volume_type['extra_specs'],
'The fetched Volume_type is different '
'from the created Volume_type')
+
+ @test.attr(type='smoke')
+ def test_volume_type_encryption_create_get(self):
+ # Create/get encryption type.
+ provider = "LuksEncryptor"
+ control_location = "front-end"
+ name = data_utils.rand_name("volume-type-")
+ resp, body = self.client.create_volume_type(name)
+ self.assertEqual(200, resp.status)
+ self.addCleanup(self._delete_volume_type, body['id'])
+ resp, encryption_type = self.client.create_encryption_type(
+ body['id'], provider=provider,
+ control_location=control_location)
+ self.assertEqual(200, resp.status)
+ self.assertIn('volume_type_id', encryption_type)
+ self.assertEqual(provider, encryption_type['provider'],
+ "The created encryption_type provider is not equal "
+ "to the requested provider")
+ self.assertEqual(control_location, encryption_type['control_location'],
+ "The created encryption_type control_location is not "
+ "equal to the requested control_location")
+ resp, fetched_encryption_type = self.client.get_encryption_type(
+ encryption_type['volume_type_id'])
+ self.assertEqual(200, resp.status)
+ self.assertEqual(provider,
+ fetched_encryption_type['provider'],
+ 'The fetched encryption_type provider is different '
+ 'from the created encryption_type')
+ self.assertEqual(control_location,
+ fetched_encryption_type['control_location'],
+ 'The fetched encryption_type control_location is '
+ 'different from the created encryption_type')
diff --git a/tempest/api/volume/admin/test_volumes_actions.py b/tempest/api/volume/admin/test_volumes_actions.py
index aa00700..4496f18 100644
--- a/tempest/api/volume/admin/test_volumes_actions.py
+++ b/tempest/api/volume/admin/test_volumes_actions.py
@@ -22,6 +22,7 @@
_interface = "json"
@classmethod
+ @test.safe_setup
def setUpClass(cls):
super(VolumesActionsTest, cls).setUpClass()
cls.client = cls.volumes_client
diff --git a/tempest/api/volume/base.py b/tempest/api/volume/base.py
index 8824977..2c6050c 100644
--- a/tempest/api/volume/base.py
+++ b/tempest/api/volume/base.py
@@ -145,6 +145,7 @@
cls.os_adm = clients.AdminManager(interface=cls._interface)
cls.client = cls.os_adm.volume_types_client
cls.hosts_client = cls.os_adm.volume_hosts_client
+ cls.quotas_client = cls.os_adm.volume_quotas_client
class BaseVolumeV2Test(BaseVolumeTest):
diff --git a/tempest/api/volume/test_snapshot_metadata.py b/tempest/api/volume/test_snapshot_metadata.py
index 1493b37..d2c4ab7 100644
--- a/tempest/api/volume/test_snapshot_metadata.py
+++ b/tempest/api/volume/test_snapshot_metadata.py
@@ -21,6 +21,7 @@
_interface = "json"
@classmethod
+ @test.safe_setup
def setUpClass(cls):
super(SnapshotMetadataTest, cls).setUpClass()
cls.client = cls.snapshots_client
diff --git a/tempest/api/volume/test_volumes_actions.py b/tempest/api/volume/test_volumes_actions.py
index a22ad32..cfab0bd 100644
--- a/tempest/api/volume/test_volumes_actions.py
+++ b/tempest/api/volume/test_volumes_actions.py
@@ -25,6 +25,7 @@
_interface = "json"
@classmethod
+ @test.safe_setup
def setUpClass(cls):
super(VolumesActionsTest, cls).setUpClass()
cls.client = cls.volumes_client
@@ -44,7 +45,7 @@
def tearDownClass(cls):
# Delete the test instance
cls.servers_client.delete_server(cls.server['id'])
- cls.client.wait_for_resource_deletion(cls.server['id'])
+ cls.servers_client.wait_for_server_termination(cls.server['id'])
super(VolumesActionsTest, cls).tearDownClass()
diff --git a/tempest/api/volume/test_volumes_get.py b/tempest/api/volume/test_volumes_get.py
index 175da01..be5d76b 100644
--- a/tempest/api/volume/test_volumes_get.py
+++ b/tempest/api/volume/test_volumes_get.py
@@ -124,8 +124,8 @@
resp, new_volume = \
self.client.create_volume(size=1,
display_description=new_v_desc,
- availability_zone=volume[
- 'availability_zone'])
+ availability_zone=
+ volume['availability_zone'])
self.assertEqual(200, resp.status)
self.assertIn('id', new_volume)
self.addCleanup(self._delete_volume, new_volume['id'])
@@ -133,8 +133,8 @@
resp, update_volume = \
self.client.update_volume(new_volume['id'],
display_name=volume['display_name'],
- display_description=volume[
- 'display_description'])
+ display_description=
+ volume['display_description'])
self.assertEqual(200, resp.status)
# NOTE(jdg): Revert back to strict true/false checking
diff --git a/tempest/api/volume/test_volumes_snapshots.py b/tempest/api/volume/test_volumes_snapshots.py
index 2701e84..84c9501 100644
--- a/tempest/api/volume/test_volumes_snapshots.py
+++ b/tempest/api/volume/test_volumes_snapshots.py
@@ -71,8 +71,8 @@
resp, server = self.servers_client.create_server(server_name,
self.image_ref,
self.flavor_ref)
- self.servers_client.wait_for_server_status(server['id'], 'ACTIVE')
self.addCleanup(self.servers_client.delete_server, server['id'])
+ self.servers_client.wait_for_server_status(server['id'], 'ACTIVE')
mountpoint = '/dev/%s' % CONF.compute.volume_device_name
resp, body = self.volumes_client.attach_volume(
self.volume_origin['id'], server['id'], mountpoint)
diff --git a/tempest/api/volume/v2/test_volumes_list.py b/tempest/api/volume/v2/test_volumes_list.py
index 0e91371..4d2573b 100644
--- a/tempest/api/volume/v2/test_volumes_list.py
+++ b/tempest/api/volume/v2/test_volumes_list.py
@@ -116,8 +116,8 @@
('details' if with_detail else '', key)
if key == 'metadata':
self.assertThat(volume[key].items(),
- matchers.ContainsAll(params[key]
- .items()), msg)
+ matchers.ContainsAll(
+ params[key].items()), msg)
else:
self.assertEqual(params[key], volume[key], msg)
@@ -187,7 +187,7 @@
self._list_by_param_value_and_assert(params)
@test.attr(type='gate')
- def test_volume_list_with_detail_param_metadata(self):
+ def test_volume_list_with_details_param_metadata(self):
# Test to list volumes details when metadata param is given
params = {'metadata': self.metadata}
self._list_by_param_value_and_assert(params, with_detail=True)
@@ -201,7 +201,7 @@
self._list_by_param_value_and_assert(params, expected_list=[volume])
@test.attr(type='gate')
- def test_volume_list_with_detail_param_display_name_and_status(self):
+ def test_volume_list_with_details_param_display_name_and_status(self):
# Test to list volume when name and status param is given
volume = self.volume_list[data_utils.rand_int_id(0, 2)]
params = {'name': volume['name'],
@@ -209,6 +209,37 @@
self._list_by_param_value_and_assert(params, expected_list=[volume],
with_detail=True)
+ @test.attr(type='gate')
+ def test_volume_list_details_with_multiple_params(self):
+ # List volumes detail using combined condition
+ def _list_details_with_multiple_params(limit=2,
+ status='available',
+ sort_dir='asc',
+ sort_key='created_at'):
+ params = {'limit': limit,
+ 'status': status,
+ 'sort_dir': sort_dir,
+ 'sort_key': sort_key
+ }
+ resp, fetched_volume = self.client.list_volumes_with_detail(params)
+ self.assertEqual(200, resp.status)
+ self.assertEqual(limit, len(fetched_volume),
+ "The count of volumes is %s, expected:%s " %
+ (len(fetched_volume), limit))
+ self.assertEqual(status, fetched_volume[0]['status'])
+ self.assertEqual(status, fetched_volume[1]['status'])
+ val0 = fetched_volume[0][sort_key]
+ val1 = fetched_volume[1][sort_key]
+ if sort_dir == 'asc':
+ self.assertTrue(val0 < val1,
+ "%s < %s" % (val0, val1))
+ elif sort_dir == 'desc':
+ self.assertTrue(val0 > val1,
+ "%s > %s" % (val0, val1))
+
+ _list_details_with_multiple_params()
+ _list_details_with_multiple_params(sort_dir='desc')
+
class VolumesV2ListTestXML(VolumesV2ListTestJSON):
_interface = 'xml'
diff --git a/tempest/api_schema/__init__.py b/tempest/api_schema/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/api_schema/__init__.py
diff --git a/tempest/api_schema/compute/__init__.py b/tempest/api_schema/compute/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/api_schema/compute/__init__.py
diff --git a/tempest/api_schema/compute/hosts.py b/tempest/api_schema/compute/hosts.py
new file mode 100644
index 0000000..b9a3db9
--- /dev/null
+++ b/tempest/api_schema/compute/hosts.py
@@ -0,0 +1,35 @@
+# Copyright 2014 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.
+
+list_hosts = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'hosts': {
+ 'type': 'array',
+ 'items': {
+ 'type': 'object',
+ 'properties': {
+ 'host_name': {'type': 'string'},
+ 'service': {'type': 'string'},
+ 'zone': {'type': 'string'}
+ },
+ 'required': ['host_name', 'service', 'zone']
+ }
+ }
+ },
+ 'required': ['hosts']
+ }
+}
diff --git a/tempest/api_schema/compute/services.py b/tempest/api_schema/compute/services.py
new file mode 100644
index 0000000..4793f5a
--- /dev/null
+++ b/tempest/api_schema/compute/services.py
@@ -0,0 +1,44 @@
+# Copyright 2014 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.
+
+list_services = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'services': {
+ 'type': 'array',
+ 'items': {
+ 'type': 'object',
+ 'properties': {
+ # NOTE: Now the type of 'id' is integer, but here
+ # allows 'string' also because we will be able to
+ # change it to 'uuid' in the future.
+ 'id': {'type': ['integer', 'string']},
+ 'zone': {'type': 'string'},
+ 'host': {'type': 'string'},
+ 'state': {'type': 'string'},
+ 'binary': {'type': 'string'},
+ 'status': {'type': 'string'},
+ 'updated_at': {'type': 'string'},
+ 'disabled_reason': {'type': ['string', 'null']}
+ },
+ 'required': ['id', 'zone', 'host', 'state', 'binary',
+ 'status', 'updated_at', 'disabled_reason']
+ }
+ }
+ },
+ 'required': ['services']
+ }
+}
diff --git a/tempest/api_schema/compute/v2/__init__.py b/tempest/api_schema/compute/v2/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/api_schema/compute/v2/__init__.py
diff --git a/tempest/api_schema/compute/v2/fixed_ips.py b/tempest/api_schema/compute/v2/fixed_ips.py
new file mode 100644
index 0000000..a6add04
--- /dev/null
+++ b/tempest/api_schema/compute/v2/fixed_ips.py
@@ -0,0 +1,36 @@
+# Copyright 2014 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.
+
+fixed_ips = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'fixed_ip': {
+ 'type': 'object',
+ 'properties': {
+ 'address': {
+ 'type': 'string',
+ 'format': 'ip-address'
+ },
+ 'cidr': {'type': 'string'},
+ 'host': {'type': 'string'},
+ 'hostname': {'type': 'string'}
+ },
+ 'required': ['address', 'cidr', 'host', 'hostname']
+ }
+ },
+ 'required': ['fixed_ip']
+ }
+}
diff --git a/tempest/api_schema/compute/v2/floating_ips.py b/tempest/api_schema/compute/v2/floating_ips.py
new file mode 100644
index 0000000..61582ec
--- /dev/null
+++ b/tempest/api_schema/compute/v2/floating_ips.py
@@ -0,0 +1,46 @@
+# Copyright 2014 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.
+
+list_floating_ips = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'floating_ips': {
+ 'type': 'array',
+ 'items': {
+ 'type': 'object',
+ 'properties': {
+ # NOTE: Now the type of 'id' is integer, but
+ # here allows 'string' also because we will be
+ # able to change it to 'uuid' in the future.
+ 'id': {'type': ['integer', 'string']},
+ 'pool': {'type': ['string', 'null']},
+ 'instance_id': {'type': ['integer', 'string', 'null']},
+ 'ip': {
+ 'type': 'string',
+ 'format': 'ip-address'
+ },
+ 'fixed_ip': {
+ 'type': ['string', 'null'],
+ 'format': 'ip-address'
+ }
+ },
+ 'required': ['id', 'pool', 'instance_id', 'ip', 'fixed_ip']
+ }
+ }
+ },
+ 'required': ['floating_ips']
+ }
+}
diff --git a/tempest/api_schema/compute/v2/images.py b/tempest/api_schema/compute/v2/images.py
new file mode 100644
index 0000000..fb4804d
--- /dev/null
+++ b/tempest/api_schema/compute/v2/images.py
@@ -0,0 +1,81 @@
+# Copyright 2014 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.
+
+get_image = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'image': {
+ 'type': 'object',
+ 'properties': {
+ 'id': {'type': 'string'},
+ 'status': {'type': 'string'},
+ 'updated': {'type': 'string'},
+ 'links': {
+ 'type': 'array',
+ 'items': {
+ 'type': 'object',
+ 'properties': {
+ 'href': {
+ 'type': 'string',
+ 'format': 'uri'
+ },
+ 'rel': {'type': 'string'}
+ },
+ 'required': ['href', 'rel']
+ }
+ },
+ 'name': {'type': 'string'},
+ 'created': {'type': 'string'},
+ 'OS-EXT-IMG-SIZE:size': {'type': 'integer'},
+ 'minDisk': {'type': 'integer'},
+ 'minRam': {'type': 'integer'},
+ 'progress': {'type': 'integer'},
+ 'metadata': {'type': 'object'},
+ 'server': {
+ 'type': 'object',
+ 'properties': {
+ # NOTE: Now the type of 'id' is integer, but here
+ # allows 'string' also because we will be able to
+ # change it to 'uuid' in the future.
+ 'id': {'type': ['integer', 'string']},
+ 'links': {
+ 'type': 'array',
+ 'items': {
+ 'type': 'object',
+ 'properties': {
+ 'href': {
+ 'type': 'string',
+ 'format': 'uri'
+ },
+ 'rel': {'type': 'string'}
+ },
+ 'required': ['href', 'rel']
+ }
+ }
+ },
+ 'required': ['id', 'links']
+ }
+ },
+ # 'server' attributes only comes in response body if image is
+ # associated with any server. So it is not 'required'
+ 'required': ['id', 'status', 'updated', 'links', 'name',
+ 'created', 'OS-EXT-IMG-SIZE:size', 'minDisk',
+ 'minRam', 'progress', 'metadata']
+ }
+ },
+ 'required': ['image']
+ }
+}
diff --git a/tempest/api_schema/compute/v2/limits.py b/tempest/api_schema/compute/v2/limits.py
new file mode 100644
index 0000000..b9857f1
--- /dev/null
+++ b/tempest/api_schema/compute/v2/limits.py
@@ -0,0 +1,94 @@
+# Copyright 2014 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.
+
+get_limit = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'limits': {
+ 'type': 'object',
+ 'properties': {
+ 'absolute': {
+ 'type': 'object',
+ 'properties': {
+ 'maxTotalRAMSize': {'type': 'integer'},
+ 'totalCoresUsed': {'type': 'integer'},
+ 'maxTotalInstances': {'type': 'integer'},
+ 'maxTotalFloatingIps': {'type': 'integer'},
+ 'totalSecurityGroupsUsed': {'type': 'integer'},
+ 'maxTotalCores': {'type': 'integer'},
+ 'totalFloatingIpsUsed': {'type': 'integer'},
+ 'maxSecurityGroups': {'type': 'integer'},
+ 'maxServerMeta': {'type': 'integer'},
+ 'maxPersonality': {'type': 'integer'},
+ 'maxImageMeta': {'type': 'integer'},
+ 'maxPersonalitySize': {'type': 'integer'},
+ 'maxSecurityGroupRules': {'type': 'integer'},
+ 'maxTotalKeypairs': {'type': 'integer'},
+ 'totalRAMUsed': {'type': 'integer'},
+ 'totalInstancesUsed': {'type': 'integer'}
+ },
+ 'required': ['maxImageMeta',
+ 'maxPersonality',
+ 'maxPersonalitySize',
+ 'maxSecurityGroupRules',
+ 'maxSecurityGroups',
+ 'maxServerMeta',
+ 'maxTotalCores',
+ 'maxTotalFloatingIps',
+ 'maxTotalInstances',
+ 'maxTotalKeypairs',
+ 'maxTotalRAMSize',
+ 'totalCoresUsed',
+ 'totalFloatingIpsUsed',
+ 'totalInstancesUsed',
+ 'totalRAMUsed',
+ 'totalSecurityGroupsUsed']
+ },
+ 'rate': {
+ 'type': 'array',
+ 'items': {
+ 'type': 'object',
+ 'properties': {
+ 'limit': {
+ 'type': 'array',
+ 'items': {
+ 'type': 'object',
+ 'properties': {
+ 'next-available':
+ {'type': 'string'},
+ 'remaining':
+ {'type': 'integer'},
+ 'unit':
+ {'type': 'string'},
+ 'value':
+ {'type': 'integer'},
+ 'verb':
+ {'type': 'string'}
+ }
+ }
+ },
+ 'regex': {'type': 'string'},
+ 'uri': {'type': 'string'}
+ }
+ }
+ }
+ },
+ 'required': ['absolute', 'rate']
+ }
+ },
+ 'required': ['limits']
+ }
+}
diff --git a/tempest/api_schema/compute/v2/quotas.py b/tempest/api_schema/compute/v2/quotas.py
new file mode 100644
index 0000000..d69cbd7
--- /dev/null
+++ b/tempest/api_schema/compute/v2/quotas.py
@@ -0,0 +1,47 @@
+# Copyright 2014 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.
+
+quota_set = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'quota_set': {
+ 'type': 'object',
+ 'properties': {
+ 'id': {'type': 'string'},
+ 'instances': {'type': 'integer'},
+ 'cores': {'type': 'integer'},
+ 'ram': {'type': 'integer'},
+ 'floating_ips': {'type': 'integer'},
+ 'fixed_ips': {'type': 'integer'},
+ 'metadata_items': {'type': 'integer'},
+ 'injected_files': {'type': 'integer'},
+ 'injected_file_content_bytes': {'type': 'integer'},
+ 'injected_file_path_bytes': {'type': 'integer'},
+ 'key_pairs': {'type': 'integer'},
+ 'security_groups': {'type': 'integer'},
+ 'security_group_rules': {'type': 'integer'}
+ },
+ 'required': ['id', 'instances', 'cores', 'ram',
+ 'floating_ips', 'fixed_ips',
+ 'metadata_items', 'injected_files',
+ 'injected_file_content_bytes',
+ 'injected_file_path_bytes', 'key_pairs',
+ 'security_groups', 'security_group_rules']
+ }
+ },
+ 'required': ['quota_set']
+ }
+}
diff --git a/tempest/api_schema/compute/v2/servers.py b/tempest/api_schema/compute/v2/servers.py
new file mode 100644
index 0000000..7f06ca6
--- /dev/null
+++ b/tempest/api_schema/compute/v2/servers.py
@@ -0,0 +1,53 @@
+# Copyright 2014 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.
+
+create_server = {
+ 'status_code': [202],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'server': {
+ 'type': 'object',
+ 'properties': {
+ # NOTE: Now the type of 'id' is uuid, but here allows
+ # 'integer' also because old OpenStack uses 'integer'
+ # as a server id.
+ 'id': {'type': ['integer', 'string']},
+ 'security_groups': {'type': 'array'},
+ 'links': {
+ 'type': 'array',
+ 'items': {
+ 'type': 'object',
+ 'properties': {
+ 'href': {
+ 'type': 'string',
+ 'format': 'uri'
+ },
+ 'rel': {'type': 'string'}
+ },
+ 'required': ['href', 'rel']
+ }
+ },
+ 'adminPass': {'type': 'string'},
+ 'OS-DCF:diskConfig': {'type': 'string'}
+ },
+ # NOTE: OS-DCF:diskConfig is API extension, and some
+ # environments return a response without the attribute.
+ # So it is not 'required'.
+ 'required': ['id', 'security_groups', 'links', 'adminPass']
+ }
+ },
+ 'required': ['server']
+ }
+}
diff --git a/tempest/api_schema/compute/v2/volumes.py b/tempest/api_schema/compute/v2/volumes.py
new file mode 100644
index 0000000..16ed7c2
--- /dev/null
+++ b/tempest/api_schema/compute/v2/volumes.py
@@ -0,0 +1,56 @@
+# Copyright 2014 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.
+
+get_volume = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'volume': {
+ 'type': 'object',
+ 'properties': {
+ # NOTE: Now the type of 'id' is integer, but here allows
+ # 'string' also because we will be able to change it to
+ # 'uuid' in the future.
+ 'id': {'type': ['integer', 'string']},
+ 'status': {'type': 'string'},
+ 'displayName': {'type': ['string', 'null']},
+ 'availabilityZone': {'type': 'string'},
+ 'createdAt': {'type': 'string'},
+ 'displayDescription': {'type': ['string', 'null']},
+ 'volumeType': {'type': 'string'},
+ 'snapshotId': {'type': ['string', 'null']},
+ 'metadata': {'type': 'object'},
+ 'size': {'type': 'integer'},
+ 'attachments': {
+ 'type': 'array',
+ 'items': {
+ 'type': 'object',
+ 'properties': {
+ 'id': {'type': ['integer', 'string']},
+ 'device': {'type': 'string'},
+ 'volumeId': {'type': ['integer', 'string']},
+ 'serverId': {'type': ['integer', 'string']}
+ }
+ }
+ }
+ },
+ 'required': ['id', 'status', 'displayName', 'availabilityZone',
+ 'createdAt', 'displayDescription', 'volumeType',
+ 'snapshotId', 'metadata', 'size', 'attachments']
+ }
+ },
+ 'required': ['volume']
+ }
+}
diff --git a/tempest/api_schema/compute/v3/__init__.py b/tempest/api_schema/compute/v3/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/api_schema/compute/v3/__init__.py
diff --git a/tempest/api_schema/compute/v3/quotas.py b/tempest/api_schema/compute/v3/quotas.py
new file mode 100644
index 0000000..1b9989d
--- /dev/null
+++ b/tempest/api_schema/compute/v3/quotas.py
@@ -0,0 +1,42 @@
+# Copyright 2014 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.
+
+quota_set = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'quota_set': {
+ 'type': 'object',
+ 'properties': {
+ 'id': {'type': 'string'},
+ 'instances': {'type': 'integer'},
+ 'cores': {'type': 'integer'},
+ 'ram': {'type': 'integer'},
+ 'floating_ips': {'type': 'integer'},
+ 'fixed_ips': {'type': 'integer'},
+ 'metadata_items': {'type': 'integer'},
+ 'key_pairs': {'type': 'integer'},
+ 'security_groups': {'type': 'integer'},
+ 'security_group_rules': {'type': 'integer'}
+ },
+ 'required': ['id', 'instances', 'cores', 'ram',
+ 'floating_ips', 'fixed_ips',
+ 'metadata_items', 'key_pairs',
+ 'security_groups', 'security_group_rules']
+ }
+ },
+ 'required': ['quota_set']
+ }
+}
diff --git a/tempest/api_schema/compute/v3/servers.py b/tempest/api_schema/compute/v3/servers.py
new file mode 100644
index 0000000..e69b25f
--- /dev/null
+++ b/tempest/api_schema/compute/v3/servers.py
@@ -0,0 +1,55 @@
+# Copyright 2014 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.
+
+create_server = {
+ 'status_code': [202],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'server': {
+ 'type': 'object',
+ 'properties': {
+ # NOTE: Now the type of 'id' is uuid, but here allows
+ # 'integer' also because old OpenStack uses 'integer'
+ # as a server id.
+ 'id': {'type': ['integer', 'string']},
+ 'os-security-groups:security_groups': {'type': 'array'},
+ 'links': {
+ 'type': 'array',
+ 'items': {
+ 'type': 'object',
+ 'properties': {
+ 'href': {
+ 'type': 'string',
+ 'format': 'uri'
+ },
+ 'rel': {'type': 'string'}
+ },
+ 'required': ['href', 'rel']
+ }
+ },
+ 'admin_password': {'type': 'string'},
+ 'os-access-ips:access_ip_v4': {'type': 'string'},
+ 'os-access-ips:access_ip_v6': {'type': 'string'}
+ },
+ # NOTE: os-access-ips:access_ip_v4/v6 are API extension,
+ # and some environments return a response without these
+ # attributes. So they are not 'required'.
+ 'required': ['id', 'os-security-groups:security_groups',
+ 'links', 'admin_password']
+ }
+ },
+ 'required': ['server']
+ }
+}
diff --git a/tempest/cli/__init__.py b/tempest/cli/__init__.py
index 8c4ec45..932b151 100644
--- a/tempest/cli/__init__.py
+++ b/tempest/cli/__init__.py
@@ -42,6 +42,7 @@
def nova(self, action, flags='', params='', admin=True, fail_ok=False):
"""Executes nova command for the given action."""
+ flags += ' --endpoint-type %s' % CONF.compute.endpoint_type
return self.cmd_with_auth(
'nova', action, flags, params, admin, fail_ok)
@@ -58,34 +59,41 @@
def glance(self, action, flags='', params='', admin=True, fail_ok=False):
"""Executes glance command for the given action."""
+ flags += ' --os-endpoint-type %s' % CONF.image.endpoint_type
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."""
+ flags += ' --os-endpoint-type %s' % CONF.telemetry.endpoint_type
return self.cmd_with_auth(
'ceilometer', action, flags, params, admin, fail_ok)
def heat(self, action, flags='', params='', admin=True,
fail_ok=False):
"""Executes heat command for the given action."""
+ flags += ' --os-endpoint-type %s' % CONF.orchestration.endpoint_type
return self.cmd_with_auth(
'heat', action, flags, params, admin, fail_ok)
def cinder(self, action, flags='', params='', admin=True, fail_ok=False):
"""Executes cinder command for the given action."""
+ flags += ' --endpoint-type %s' % CONF.volume.endpoint_type
return self.cmd_with_auth(
'cinder', action, flags, params, admin, fail_ok)
def neutron(self, action, flags='', params='', admin=True, fail_ok=False):
"""Executes neutron command for the given action."""
+ flags += ' --endpoint-type %s' % CONF.network.endpoint_type
return self.cmd_with_auth(
'neutron', action, flags, params, admin, fail_ok)
- def savanna(self, action, flags='', params='', admin=True, fail_ok=False):
- """Executes savanna command for the given action."""
+ def sahara(self, action, flags='', params='', admin=True, fail_ok=False):
+ """Executes sahara command for the given action."""
+ flags += ' --endpoint-type %s' % CONF.data_processing.endpoint_type
return self.cmd_with_auth(
+ # TODO (slukjanov): replace with sahara when new client released
'savanna', action, flags, params, admin, fail_ok)
def cmd_with_auth(self, cmd, action, flags='', params='',
@@ -93,7 +101,7 @@
"""Executes given command with auth attributes appended."""
# TODO(jogo) make admin=False work
creds = ('--os-username %s --os-tenant-name %s --os-password %s '
- '--os-auth-url %s ' %
+ '--os-auth-url %s' %
(CONF.identity.admin_username,
CONF.identity.admin_tenant_name,
CONF.identity.admin_password,
diff --git a/tempest/cli/simple_read_only/test_ceilometer.py b/tempest/cli/simple_read_only/test_ceilometer.py
index 0b6ae22..1d2822d 100644
--- a/tempest/cli/simple_read_only/test_ceilometer.py
+++ b/tempest/cli/simple_read_only/test_ceilometer.py
@@ -16,6 +16,7 @@
from tempest import cli
from tempest import config
from tempest.openstack.common import log as logging
+from tempest import test
CONF = config.CONF
@@ -41,6 +42,7 @@
def test_ceilometer_meter_list(self):
self.ceilometer('meter-list')
+ @test.attr(type='slow')
def test_ceilometer_resource_list(self):
self.ceilometer('resource-list')
diff --git a/tempest/cli/simple_read_only/test_nova.py b/tempest/cli/simple_read_only/test_nova.py
index d0b6028..a3787ab 100644
--- a/tempest/cli/simple_read_only/test_nova.py
+++ b/tempest/cli/simple_read_only/test_nova.py
@@ -182,6 +182,10 @@
self.nova('agent-list')
self.nova('agent-list', flags='--debug')
+ def test_migration_list(self):
+ self.nova('migration-list')
+ self.nova('migration-list', flags='--debug')
+
# Optional arguments:
def test_admin_version(self):
diff --git a/tempest/cli/simple_read_only/test_savanna.py b/tempest/cli/simple_read_only/test_sahara.py
similarity index 61%
rename from tempest/cli/simple_read_only/test_savanna.py
rename to tempest/cli/simple_read_only/test_sahara.py
index 1e30978..cd819a4 100644
--- a/tempest/cli/simple_read_only/test_savanna.py
+++ b/tempest/cli/simple_read_only/test_sahara.py
@@ -25,8 +25,8 @@
LOG = logging.getLogger(__name__)
-class SimpleReadOnlySavannaClientTest(cli.ClientTestBase):
- """Basic, read-only tests for Savanna CLI client.
+class SimpleReadOnlySaharaClientTest(cli.ClientTestBase):
+ """Basic, read-only tests for Sahara CLI client.
Checks return values and output of read-only commands.
These tests do not presume any content, nor do they create
@@ -35,36 +35,36 @@
@classmethod
def setUpClass(cls):
- if not CONF.service_available.savanna:
- msg = "Skipping all Savanna cli tests because it is not available"
+ if not CONF.service_available.sahara:
+ msg = "Skipping all Sahara cli tests because it is not available"
raise cls.skipException(msg)
- super(SimpleReadOnlySavannaClientTest, cls).setUpClass()
+ super(SimpleReadOnlySaharaClientTest, cls).setUpClass()
@test.attr(type='negative')
- def test_savanna_fake_action(self):
+ def test_sahara_fake_action(self):
self.assertRaises(subprocess.CalledProcessError,
- self.savanna,
+ self.sahara,
'this-does-not-exist')
- def test_savanna_plugins_list(self):
- plugins = self.parser.listing(self.savanna('plugin-list'))
+ def test_sahara_plugins_list(self):
+ plugins = self.parser.listing(self.sahara('plugin-list'))
self.assertTableStruct(plugins, ['name', 'versions', 'title'])
- def test_savanna_plugins_show(self):
- plugin = self.parser.listing(self.savanna('plugin-show',
- params='--name vanilla'))
+ def test_sahara_plugins_show(self):
+ plugin = self.parser.listing(self.sahara('plugin-show',
+ params='--name vanilla'))
self.assertTableStruct(plugin, ['Property', 'Value'])
- def test_savanna_node_group_template_list(self):
- plugins = self.parser.listing(self.savanna('node-group-template-list'))
+ def test_sahara_node_group_template_list(self):
+ plugins = self.parser.listing(self.sahara('node-group-template-list'))
self.assertTableStruct(plugins, ['name', 'id', 'plugin_name',
'node_processes', 'description'])
- def test_savanna_cluster_template_list(self):
- plugins = self.parser.listing(self.savanna('cluster-template-list'))
+ def test_sahara_cluster_template_list(self):
+ plugins = self.parser.listing(self.sahara('cluster-template-list'))
self.assertTableStruct(plugins, ['name', 'id', 'plugin_name',
'node_groups', 'description'])
- def test_savanna_cluster_list(self):
- plugins = self.parser.listing(self.savanna('cluster-list'))
+ def test_sahara_cluster_list(self):
+ plugins = self.parser.listing(self.sahara('cluster-list'))
self.assertTableStruct(plugins, ['name', 'id', 'status', 'node_count'])
diff --git a/tempest/clients.py b/tempest/clients.py
index e16d0f4..7ebd983 100644
--- a/tempest/clients.py
+++ b/tempest/clients.py
@@ -61,6 +61,7 @@
TenantUsagesClientJSON
from tempest.services.compute.json.volumes_extensions_client import \
VolumesExtensionsClientJSON
+from tempest.services.compute.v3.json.agents_client import AgentsV3ClientJSON
from tempest.services.compute.v3.json.aggregates_client import \
AggregatesV3ClientJSON
from tempest.services.compute.v3.json.availability_zone_client import \
@@ -152,12 +153,15 @@
ObjectClientCustomizedHeader
from tempest.services.orchestration.json.orchestration_client import \
OrchestrationClient
+from tempest.services.queuing.json.queuing_client import QueuingClientJSON
from tempest.services.telemetry.json.telemetry_client import \
TelemetryClientJSON
from tempest.services.telemetry.xml.telemetry_client import \
TelemetryClientXML
from tempest.services.volume.json.admin.volume_hosts_client import \
VolumeHostsClientJSON
+from tempest.services.volume.json.admin.volume_quotas_client import \
+ VolumeQuotasClientJSON
from tempest.services.volume.json.admin.volume_types_client import \
VolumeTypesClientJSON
from tempest.services.volume.json.backups_client import BackupsClientJSON
@@ -169,6 +173,8 @@
from tempest.services.volume.v2.xml.volumes_client import VolumesV2ClientXML
from tempest.services.volume.xml.admin.volume_hosts_client import \
VolumeHostsClientXML
+from tempest.services.volume.xml.admin.volume_quotas_client import \
+ VolumeQuotasClientXML
from tempest.services.volume.xml.admin.volume_types_client import \
VolumeTypesClientXML
from tempest.services.volume.xml.backups_client import BackupsClientXML
@@ -249,6 +255,8 @@
InstanceUsagesAuditLogClientXML(self.auth_provider)
self.volume_hosts_client = VolumeHostsClientXML(
self.auth_provider)
+ self.volume_quotas_client = VolumeQuotasClientXML(
+ self.auth_provider)
self.volumes_extension_client = VolumeExtensionClientXML(
self.auth_provider)
if CONF.service_available.ceilometer:
@@ -308,6 +316,7 @@
self.services_v3_client = ServicesV3ClientJSON(
self.auth_provider)
self.service_client = ServiceClientJSON(self.auth_provider)
+ self.agents_v3_client = AgentsV3ClientJSON(self.auth_provider)
self.aggregates_v3_client = AggregatesV3ClientJSON(
self.auth_provider)
self.aggregates_client = AggregatesClientJSON(
@@ -329,11 +338,14 @@
InstanceUsagesAuditLogClientJSON(self.auth_provider)
self.volume_hosts_client = VolumeHostsClientJSON(
self.auth_provider)
+ self.volume_quotas_client = VolumeQuotasClientJSON(
+ self.auth_provider)
self.volumes_extension_client = VolumeExtensionClientJSON(
self.auth_provider)
self.hosts_v3_client = HostsV3ClientJSON(self.auth_provider)
self.database_flavors_client = DatabaseFlavorsClientJSON(
self.auth_provider)
+ self.queuing_client = QueuingClientJSON(self.auth_provider)
if CONF.service_available.ceilometer:
self.telemetry_client = TelemetryClientJSON(
self.auth_provider)
diff --git a/tempest/common/commands.py b/tempest/common/commands.py
index 6405eaa..c31a038 100644
--- a/tempest/common/commands.py
+++ b/tempest/common/commands.py
@@ -73,3 +73,7 @@
def iptables_ns(ns, table):
return ip_ns_exec(ns, "iptables -v -S -t " + table)
+
+
+def ovs_db_dump():
+ return sudo_cmd_call("ovsdb-client dump")
diff --git a/tempest/common/debug.py b/tempest/common/debug.py
index 8325d4d..6a496c2 100644
--- a/tempest/common/debug.py
+++ b/tempest/common/debug.py
@@ -38,3 +38,15 @@
for table in ['filter', 'nat', 'mangle']:
LOG.info('ns(%s) table(%s):\n%s', ns, table,
commands.iptables_ns(ns, table))
+
+
+def log_ovs_db():
+ if not CONF.debug.enable or not CONF.service_available.neutron:
+ return
+ db_dump = commands.ovs_db_dump()
+ LOG.info("OVS DB:\n" + db_dump)
+
+
+def log_net_debug():
+ log_ip_ns()
+ log_ovs_db()
diff --git a/tempest/common/generator/base_generator.py b/tempest/common/generator/base_generator.py
index 35f8158..7e7a2d6 100644
--- a/tempest/common/generator/base_generator.py
+++ b/tempest/common/generator/base_generator.py
@@ -59,7 +59,9 @@
"enum": ["GET", "PUT", "HEAD",
"POST", "PATCH", "DELETE", 'COPY']
},
+ "admin_client": {"type": "boolean"},
"url": {"type": "string"},
+ "default_result_code": {"type": "integer"},
"json-schema": jsonschema._utils.load_schema("draft4"),
"resources": {
"type": "array",
diff --git a/tempest/common/isolated_creds.py b/tempest/common/isolated_creds.py
index ac8b14f..c54a8e8 100644
--- a/tempest/common/isolated_creds.py
+++ b/tempest/common/isolated_creds.py
@@ -456,7 +456,8 @@
port
for port in self.ports
if (port['network_id'] == network_id and
- port['device_owner'] != 'network:router_interface')
+ port['device_owner'] != 'network:router_interface' and
+ port['device_owner'] != 'network:dhcp')
]
for port in ports_to_delete:
try:
diff --git a/tempest/common/rest_client.py b/tempest/common/rest_client.py
index 66b6fe7..88dbe58 100644
--- a/tempest/common/rest_client.py
+++ b/tempest/common/rest_client.py
@@ -21,6 +21,8 @@
import re
import time
+import jsonschema
+
from tempest.common import http
from tempest import config
from tempest import exceptions
@@ -409,7 +411,7 @@
elif ctype.lower() in TXT_ENC:
parse_resp = False
else:
- raise exceptions.RestClientException(str(resp.status))
+ raise exceptions.InvalidContentType(str(resp.status))
if resp.status == 401 or resp.status == 403:
raise exceptions.Unauthorized()
@@ -451,25 +453,26 @@
# exception.
raise exceptions.InvalidHTTPResponseBody(message)
else:
- # I'm seeing both computeFault
- # and cloudServersFault come back.
- # Will file a bug to fix, but leave as is for now.
- if 'cloudServersFault' in resp_body:
- message = resp_body['cloudServersFault']['message']
- elif 'computeFault' in resp_body:
- message = resp_body['computeFault']['message']
- elif 'error' in resp_body: # Keystone errors
- message = resp_body['error']['message']
- raise exceptions.IdentityError(message)
- elif 'message' in resp_body:
- message = resp_body['message']
+ if isinstance(resp_body, dict):
+ # I'm seeing both computeFault
+ # and cloudServersFault come back.
+ # Will file a bug to fix, but leave as is for now.
+ if 'cloudServersFault' in resp_body:
+ message = resp_body['cloudServersFault']['message']
+ elif 'computeFault' in resp_body:
+ message = resp_body['computeFault']['message']
+ elif 'error' in resp_body: # Keystone errors
+ message = resp_body['error']['message']
+ raise exceptions.IdentityError(message)
+ elif 'message' in resp_body:
+ message = resp_body['message']
+ else:
+ message = resp_body
raise exceptions.ServerFault(message)
if resp.status >= 400:
- if parse_resp:
- resp_body = self._parse_resp(resp_body)
- raise exceptions.RestClientException(str(resp.status))
+ raise exceptions.UnexpectedResponseCode(str(resp.status))
def is_absolute_limit(self, resp, resp_body):
if (not isinstance(resp_body, collections.Mapping) or
@@ -501,6 +504,31 @@
% self.__class__.__name__)
raise NotImplementedError(message)
+ @classmethod
+ def validate_response(cls, schema, resp, body):
+ # Only check the response if the status code is a success code
+ # TODO(cyeoh): Eventually we should be able to verify that a failure
+ # code if it exists is something that we expect. This is explicitly
+ # declared in the V3 API and so we should be able to export this in
+ # the response schema. For now we'll ignore it.
+ if str(resp.status).startswith('2'):
+ response_code = schema['status_code']
+ if resp.status not in response_code:
+ msg = ("The status code(%s) is different than the expected "
+ "one(%s)") % (resp.status, response_code)
+ raise exceptions.InvalidHttpSuccessCode(msg)
+ response_schema = schema.get('response_body')
+ if response_schema:
+ try:
+ jsonschema.validate(body, response_schema)
+ except jsonschema.ValidationError as ex:
+ msg = ("HTTP response body is invalid (%s)") % ex
+ raise exceptions.InvalidHTTPResponseBody(msg)
+ else:
+ if body:
+ msg = ("HTTP response body should not exist (%s)") % body
+ raise exceptions.InvalidHTTPResponseBody(msg)
+
class NegativeRestClient(RestClient):
"""
diff --git a/tempest/common/utils/data_utils.py b/tempest/common/utils/data_utils.py
index cd32720..a0a88dd 100644
--- a/tempest/common/utils/data_utils.py
+++ b/tempest/common/utils/data_utils.py
@@ -15,12 +15,8 @@
import itertools
import random
-import re
-import urllib
import uuid
-from tempest import exceptions
-
def rand_uuid():
return str(uuid.uuid4())
@@ -57,37 +53,6 @@
return ':'.join(["%02x" % x for x in mac])
-def build_url(host, port, api_version=None, path=None,
- params=None, use_ssl=False):
- """Build the request URL from given host, port, path and parameters."""
-
- pattern = 'v\d\.\d'
- if re.match(pattern, path):
- message = 'Version should not be included in path.'
- raise exceptions.InvalidConfiguration(message=message)
-
- if use_ssl:
- url = "https://" + host
- else:
- url = "http://" + host
-
- if port is not None:
- url += ":" + port
- url += "/"
-
- if api_version is not None:
- url += api_version + "/"
-
- if path is not None:
- url += path
-
- if params is not None:
- url += "?"
- url += urllib.urlencode(params)
-
- return url
-
-
def parse_image_id(image_ref):
"""Return the image id from a given image ref."""
return image_ref.rsplit('/')[-1]
diff --git a/tempest/common/utils/linux/remote_client.py b/tempest/common/utils/linux/remote_client.py
index 8420ad0..00e5e0d 100644
--- a/tempest/common/utils/linux/remote_client.py
+++ b/tempest/common/utils/linux/remote_client.py
@@ -43,6 +43,9 @@
ssh_timeout, pkey=pkey,
channel_timeout=ssh_channel_timeout)
+ def exec_command(self, cmd):
+ return self.ssh_client.exec_command(cmd)
+
def validate_authentication(self):
"""Validate ssh connection and authentication
This method raises an Exception when the validation fails.
@@ -51,33 +54,33 @@
def hostname_equals_servername(self, expected_hostname):
# Get host name using command "hostname"
- actual_hostname = self.ssh_client.exec_command("hostname").rstrip()
+ actual_hostname = self.exec_command("hostname").rstrip()
return expected_hostname == actual_hostname
def get_files(self, path):
# Return a list of comma separated files
command = "ls -m " + path
- return self.ssh_client.exec_command(command).rstrip('\n').split(', ')
+ return self.exec_command(command).rstrip('\n').split(', ')
def get_ram_size_in_mb(self):
- output = self.ssh_client.exec_command('free -m | grep Mem')
+ output = self.exec_command('free -m | grep Mem')
if output:
return output.split()[1]
def get_number_of_vcpus(self):
command = 'cat /proc/cpuinfo | grep processor | wc -l'
- output = self.ssh_client.exec_command(command)
+ output = self.exec_command(command)
return int(output)
def get_partitions(self):
# Return the contents of /proc/partitions
command = 'cat /proc/partitions'
- output = self.ssh_client.exec_command(command)
+ output = self.exec_command(command)
return output
def get_boot_time(self):
cmd = 'cut -f1 -d. /proc/uptime'
- boot_secs = self.ssh_client.exec_command(cmd)
+ boot_secs = self.exec_command(cmd)
boot_time = time.time() - int(boot_secs)
return time.localtime(boot_time)
@@ -85,27 +88,27 @@
message = re.sub("([$\\`])", "\\\\\\\\\\1", message)
# usually to /dev/ttyS0
cmd = 'sudo sh -c "echo \\"%s\\" >/dev/console"' % message
- return self.ssh_client.exec_command(cmd)
+ return self.exec_command(cmd)
def ping_host(self, host):
cmd = 'ping -c1 -w1 %s' % host
- return self.ssh_client.exec_command(cmd)
+ return self.exec_command(cmd)
def get_mac_address(self):
cmd = "/sbin/ifconfig | awk '/HWaddr/ {print $5}'"
- return self.ssh_client.exec_command(cmd)
+ return self.exec_command(cmd)
def get_ip_list(self):
cmd = "/bin/ip address"
- return self.ssh_client.exec_command(cmd)
+ return self.exec_command(cmd)
def assign_static_ip(self, nic, addr):
cmd = "sudo /bin/ip addr add {ip}/{mask} dev {nic}".format(
ip=addr, mask=CONF.network.tenant_network_mask_bits,
nic=nic
)
- return self.ssh_client.exec_command(cmd)
+ return self.exec_command(cmd)
def turn_nic_on(self, nic):
cmd = "sudo /bin/ip link set {nic} up".format(nic=nic)
- return self.ssh_client.exec_command(cmd)
+ return self.exec_command(cmd)
diff --git a/tempest/config.py b/tempest/config.py
index 9f279fb..801fcdf 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -60,16 +60,16 @@
'publicURL', 'adminURL', 'internalURL'],
help="The endpoint type to use for the identity service."),
cfg.StrOpt('username',
- default='demo',
+ default=None,
help="Username to use for Nova API requests."),
cfg.StrOpt('tenant_name',
- default='demo',
+ default=None,
help="Tenant name to use for Nova API requests."),
cfg.StrOpt('admin_role',
default='admin',
help="Role required to administrate keystone."),
cfg.StrOpt('password',
- default='pass',
+ default=None,
help="API key to use when authenticating.",
secret=True),
cfg.StrOpt('alt_username',
@@ -85,15 +85,15 @@
help="API key to use when authenticating as alternate user.",
secret=True),
cfg.StrOpt('admin_username',
- default='admin',
+ default=None,
help="Administrative Username to use for "
"Keystone API requests."),
cfg.StrOpt('admin_tenant_name',
- default='admin',
+ default=None,
help="Administrative Tenant name to use for Keystone API "
"requests."),
cfg.StrOpt('admin_password',
- default='pass',
+ default=None,
help="API key to use when authenticating as admin.",
secret=True),
]
@@ -246,9 +246,6 @@
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?"),
@@ -279,14 +276,14 @@
ComputeAdminGroup = [
cfg.StrOpt('username',
- default='admin',
+ default=None,
help="Administrative Username to use for Nova API requests."),
cfg.StrOpt('tenant_name',
- default='admin',
+ default=None,
help="Administrative Tenant name to use for Nova API "
"requests."),
cfg.StrOpt('password',
- default='pass',
+ default=None,
help="API key to use when authenticating as admin.",
secret=True),
]
@@ -369,6 +366,14 @@
default="",
help="Id of the public router that provides external "
"connectivity"),
+ cfg.IntOpt('build_timeout',
+ default=300,
+ help="Timeout in seconds to wait for network operation to "
+ "complete."),
+ cfg.IntOpt('build_interval',
+ default=10,
+ help="Time in seconds between network operation status "
+ "checks."),
]
network_feature_group = cfg.OptGroup(name='network-feature-enabled',
@@ -384,6 +389,15 @@
'entry all which indicates every extension is enabled'),
]
+queuing_group = cfg.OptGroup(name='queuing',
+ title='Queuing Service')
+
+QueuingGroup = [
+ cfg.StrOpt('catalog_type',
+ default='queuing',
+ help='Catalog type of the Queuing service.'),
+]
+
volume_group = cfg.OptGroup(name='volume',
title='Block Storage Options')
@@ -615,6 +629,9 @@
cfg.StrOpt('aws_access',
default=None,
help="AWS Access Key"),
+ cfg.StrOpt('aws_zone',
+ default="nova",
+ help="AWS Zone for EC2 tests"),
cfg.StrOpt('s3_materials_path',
default="/opt/stack/devstack/files/images/"
"s3-materials/cirros-0.3.0",
@@ -747,15 +764,18 @@
cfg.BoolOpt('horizon',
default=True,
help="Whether or not Horizon is expected to be available"),
- cfg.BoolOpt('savanna',
+ cfg.BoolOpt('sahara',
default=False,
- help="Whether or not Savanna is expected to be available"),
+ help="Whether or not Sahara is expected to be available"),
cfg.BoolOpt('ironic',
default=False,
help="Whether or not Ironic is expected to be available"),
cfg.BoolOpt('trove',
default=False,
help="Whether or not Trove is expected to be available"),
+ cfg.BoolOpt('marconi',
+ default=False,
+ help="Whether or not Marconi is expected to be available"),
]
debug_group = cfg.OptGroup(name="debug",
@@ -845,6 +865,7 @@
register_opt_group(cfg.CONF, network_group, NetworkGroup)
register_opt_group(cfg.CONF, network_feature_group,
NetworkFeaturesGroup)
+ register_opt_group(cfg.CONF, queuing_group, QueuingGroup)
register_opt_group(cfg.CONF, volume_group, VolumeGroup)
register_opt_group(cfg.CONF, volume_feature_group,
VolumeFeaturesGroup)
@@ -896,6 +917,7 @@
'object-storage-feature-enabled']
self.database = cfg.CONF.database
self.orchestration = cfg.CONF.orchestration
+ self.queuing = cfg.CONF.queuing
self.telemetry = cfg.CONF.telemetry
self.dashboard = cfg.CONF.dashboard
self.data_processing = cfg.CONF.data_processing
diff --git a/tempest/exceptions/__init__.py b/tempest/exceptions/__init__.py
index c95f94f..485f532 100644
--- a/tempest/exceptions/__init__.py
+++ b/tempest/exceptions/__init__.py
@@ -20,6 +20,10 @@
message = "Invalid Configuration"
+class InvalidCredentials(base.TempestException):
+ message = "Invalid Credentials"
+
+
class InvalidHttpSuccessCode(base.RestClientException):
message = "The success code is different than the expected one"
@@ -146,3 +150,11 @@
class InvalidHTTPResponseBody(base.RestClientException):
message = "HTTP response body is invalid json or xml"
+
+
+class InvalidContentType(base.RestClientException):
+ message = "Invalid content type provided"
+
+
+class UnexpectedResponseCode(base.RestClientException):
+ message = "Unexpected response code received"
diff --git a/tempest/hacking/checks.py b/tempest/hacking/checks.py
index 55be60a..7f39905 100644
--- a/tempest/hacking/checks.py
+++ b/tempest/hacking/checks.py
@@ -15,7 +15,9 @@
import re
-PYTHON_CLIENTS = ['cinder', 'glance', 'keystone', 'nova', 'swift', 'neutron']
+PYTHON_CLIENTS = ['cinder', 'glance', 'keystone', 'nova', 'swift', 'neutron',
+ 'trove', 'ironic', 'savanna', 'heat', 'ceilometer',
+ 'marconi']
PYTHON_CLIENT_RE = re.compile('import (%s)client' % '|'.join(PYTHON_CLIENTS))
TEST_DEFINITION = re.compile(r'^\s*def test.*')
diff --git a/tempest/manager.py b/tempest/manager.py
index 708447e..63235db 100644
--- a/tempest/manager.py
+++ b/tempest/manager.py
@@ -75,13 +75,12 @@
tenant_name=CONF.identity.tenant_name
)
- def get_auth_provider(self, credentials=None):
- auth_params = dict(client_type=getattr(self, 'client_type', None),
- interface=getattr(self, 'interface', None))
+ def get_auth_provider(self, credentials):
+ if credentials is None:
+ raise exceptions.InvalidCredentials(
+ 'Credentials must be specified')
auth_provider_class = self.get_auth_provider_class(self.auth_version)
- # If invalid / incomplete credentials are provided, use default ones
- if credentials is None or \
- not auth_provider_class.check_credentials(credentials):
- credentials = self.credentials
- auth_params['credentials'] = credentials
- return auth_provider_class(**auth_params)
+ return auth_provider_class(
+ client_type=getattr(self, 'client_type', None),
+ interface=getattr(self, 'interface', None),
+ credentials=credentials)
diff --git a/tempest/scenario/test_load_balancer_basic.py b/tempest/scenario/test_load_balancer_basic.py
index ce2c66f..f7a3d6f 100644
--- a/tempest/scenario/test_load_balancer_basic.py
+++ b/tempest/scenario/test_load_balancer_basic.py
@@ -215,6 +215,7 @@
self.assertEqual(5, resp.count("server1\n"))
self.assertEqual(5, resp.count("server2\n"))
+ @test.skip_because(bug='1295165')
@test.attr(type='smoke')
@test.services('compute', 'network')
def test_load_balancer_basic(self):
diff --git a/tempest/scenario/test_minimum_basic.py b/tempest/scenario/test_minimum_basic.py
index 39b7760..24d2677 100644
--- a/tempest/scenario/test_minimum_basic.py
+++ b/tempest/scenario/test_minimum_basic.py
@@ -97,7 +97,7 @@
except Exception:
LOG.exception('ssh to server failed')
self._log_console_output()
- debug.log_ip_ns()
+ debug.log_net_debug()
raise
def check_partitions(self):
diff --git a/tempest/scenario/test_network_basic_ops.py b/tempest/scenario/test_network_basic_ops.py
index e441415..d5ab3d3 100644
--- a/tempest/scenario/test_network_basic_ops.py
+++ b/tempest/scenario/test_network_basic_ops.py
@@ -172,7 +172,7 @@
except Exception:
LOG.exception('Tenant connectivity check failed')
self._log_console_output(servers=self.servers.keys())
- debug.log_ip_ns()
+ debug.log_net_debug()
raise
def _create_and_associate_floating_ips(self):
@@ -204,7 +204,7 @@
ex_msg += ": " + msg
LOG.exception(ex_msg)
self._log_console_output(servers=self.servers.keys())
- debug.log_ip_ns()
+ debug.log_net_debug()
raise
def _disassociate_floating_ips(self):
@@ -276,24 +276,46 @@
ipatxt = ssh_client.get_ip_list()
return reg.findall(ipatxt)
- def _check_network_internal_connectivity(self):
+ def _check_network_internal_connectivity(self, network):
"""
via ssh check VM internal connectivity:
- - ping internal DHCP port, implying in-tenant connectivty
+ - ping internal gateway and DHCP port, implying in-tenant connectivity
+ pinging both, because L3 and DHCP agents might be on different nodes
"""
floating_ip, server = self.floating_ip_tuple
# get internal ports' ips:
# get all network ports in the new network
internal_ips = (p['fixed_ips'][0]['ip_address'] for p in
self._list_ports(tenant_id=server.tenant_id,
- network_id=self.new_net.id)
+ network_id=network.id)
if p['device_owner'].startswith('network'))
+ self._check_server_connectivity(floating_ip, internal_ips)
+
+ def _check_network_external_connectivity(self):
+ """
+ ping public network default gateway to imply external connectivity
+
+ """
+ if not CONF.network.public_network_id:
+ msg = 'public network not defined.'
+ LOG.info(msg)
+ return
+
+ subnet = self.network_client.list_subnets(
+ network_id=CONF.network.public_network_id)['subnets']
+ self.assertEqual(1, len(subnet), "Found %d subnets" % len(subnet))
+
+ external_ips = [subnet[0]['gateway_ip']]
+ self._check_server_connectivity(self.floating_ip_tuple.floating_ip,
+ external_ips)
+
+ def _check_server_connectivity(self, floating_ip, address_list):
ip_address = floating_ip.floating_ip_address
- private_key = self.servers[server].private_key
+ private_key = self.servers[self.floating_ip_tuple.server].private_key
ssh_source = self._ssh_to_server(ip_address, private_key)
- for remote_ip in internal_ips:
+ for remote_ip in address_list:
try:
self.assertTrue(self._check_remote_connectivity(ssh_source,
remote_ip),
@@ -322,14 +344,6 @@
ssh server hosted at the IP address. This check guarantees
that the IP address is associated with the target VM.
- - detach the floating-ip from the VM and verify that it becomes
- unreachable
-
- - associate detached floating ip to a new VM and verify connectivity.
- VMs are created with unique keypair so connectivity also asserts that
- floating IP is associated with the new VM instead of the old one
-
- # TODO(mnewby) - Need to implement the following:
- the Tempest host can ssh into the VM via the IP address and
successfully execute the following:
@@ -341,8 +355,18 @@
- ping an internal IP address, implying connectivity to another
VM on the same network.
+ - detach the floating-ip from the VM and verify that it becomes
+ unreachable
+
+ - associate detached floating ip to a new VM and verify connectivity.
+ VMs are created with unique keypair so connectivity also asserts that
+ floating IP is associated with the new VM instead of the old one
+
+
"""
self._check_public_network_connectivity(should_connect=True)
+ self._check_network_internal_connectivity(network=self.network)
+ self._check_network_external_connectivity()
self._disassociate_floating_ips()
self._check_public_network_connectivity(should_connect=False,
msg="after disassociate "
@@ -367,4 +391,4 @@
self._check_public_network_connectivity(should_connect=True)
self._create_new_network()
self._hotplug_server()
- self._check_network_internal_connectivity()
+ self._check_network_internal_connectivity(network=self.new_net)
diff --git a/tempest/scenario/test_security_groups_basic_ops.py b/tempest/scenario/test_security_groups_basic_ops.py
index d404dd1..b9ee040 100644
--- a/tempest/scenario/test_security_groups_basic_ops.py
+++ b/tempest/scenario/test_security_groups_basic_ops.py
@@ -343,7 +343,7 @@
should_succeed),
msg)
except Exception:
- debug.log_ip_ns()
+ debug.log_net_debug()
raise
def _test_in_tenant_block(self, tenant):
diff --git a/tempest/scenario/test_snapshot_pattern.py b/tempest/scenario/test_snapshot_pattern.py
index 37beb07..562020a 100644
--- a/tempest/scenario/test_snapshot_pattern.py
+++ b/tempest/scenario/test_snapshot_pattern.py
@@ -45,11 +45,10 @@
def _ssh_to_server(self, server_or_ip):
try:
- linux_client = self.get_remote_client(server_or_ip)
+ return self.get_remote_client(server_or_ip)
except Exception:
LOG.exception()
self._log_console_output()
- return linux_client.ssh_client
def _write_timestamp(self, server_or_ip):
ssh_client = self._ssh_to_server(server_or_ip)
diff --git a/tempest/scenario/test_stamp_pattern.py b/tempest/scenario/test_stamp_pattern.py
index 841f9e1..128ec17 100644
--- a/tempest/scenario/test_stamp_pattern.py
+++ b/tempest/scenario/test_stamp_pattern.py
@@ -72,8 +72,7 @@
server.add_floating_ip(floating_ip)
def _ssh_to_server(self, server_or_ip):
- linux_client = self.get_remote_client(server_or_ip)
- return linux_client.ssh_client
+ return self.get_remote_client(server_or_ip)
def _create_volume_snapshot(self, volume):
snapshot_name = data_utils.rand_name('scenario-snapshot-')
diff --git a/tempest/scenario/test_volume_boot_pattern.py b/tempest/scenario/test_volume_boot_pattern.py
index 9a250d7..e89ea70 100644
--- a/tempest/scenario/test_volume_boot_pattern.py
+++ b/tempest/scenario/test_volume_boot_pattern.py
@@ -53,7 +53,7 @@
'block_device_mapping': bd_map,
'key_name': keypair.name
}
- return self.create_server(create_kwargs=create_kwargs)
+ return self.create_server(image='', create_kwargs=create_kwargs)
def _create_snapshot_from_volume(self, vol_id):
volume_snapshots = self.volume_client.volume_snapshots
@@ -101,14 +101,13 @@
ip = server.networks[network_name_for_ssh][0]
try:
- client = self.get_remote_client(
+ return self.get_remote_client(
ip,
private_key=keypair.private_key)
except Exception:
LOG.exception('ssh to server failed')
self._log_console_output()
raise
- return client.ssh_client
def _get_content(self, ssh_client):
return ssh_client.exec_command('cat /tmp/text')
@@ -170,3 +169,15 @@
# deletion operations to succeed
self._stop_instances([instance_2nd, instance_from_snapshot])
self._detach_volumes([volume_origin, volume])
+
+
+class TestVolumeBootPatternV2(TestVolumeBootPattern):
+ def _boot_instance_from_volume(self, vol_id, keypair):
+ bdms = [{'uuid': vol_id, 'source_type': 'volume',
+ 'destination_type': 'volume', 'boot_index': 0,
+ 'delete_on_termination': False}]
+ create_kwargs = {
+ 'block_device_mapping_v2': bdms,
+ 'key_name': keypair.name
+ }
+ return self.create_server(image='', create_kwargs=create_kwargs)
diff --git a/tempest/services/botoclients.py b/tempest/services/botoclients.py
index b52d48c..7616a99 100644
--- a/tempest/services/botoclients.py
+++ b/tempest/services/botoclients.py
@@ -179,19 +179,6 @@
'revoke_security_group',
'revoke_security_group_egress'))
- def get_good_zone(self):
- """
- :rtype: BaseString
- :return: Returns with the first available zone name
- """
- for zone in self.get_all_zones():
- # NOTE(afazekas): zone.region_name was None
- if (zone.state == "available" and
- zone.region.name == self.connection_data["region"].name):
- return zone.name
- else:
- raise IndexError("Don't have a good zone")
-
class ObjectClientS3(BotoClientBase):
diff --git a/tempest/services/compute/json/fixed_ips_client.py b/tempest/services/compute/json/fixed_ips_client.py
index 8b2c6c9..5fdd564 100644
--- a/tempest/services/compute/json/fixed_ips_client.py
+++ b/tempest/services/compute/json/fixed_ips_client.py
@@ -15,6 +15,7 @@
import json
+from tempest.api_schema.compute.v2 import fixed_ips as schema
from tempest.common import rest_client
from tempest import config
@@ -31,6 +32,7 @@
url = "os-fixed-ips/%s" % (fixed_ip)
resp, body = self.get(url)
body = json.loads(body)
+ self.validate_response(schema.fixed_ips, resp, body)
return resp, body['fixed_ip']
def reserve_fixed_ip(self, ip, body):
diff --git a/tempest/services/compute/json/floating_ips_client.py b/tempest/services/compute/json/floating_ips_client.py
index 42487c3..2a7e25a 100644
--- a/tempest/services/compute/json/floating_ips_client.py
+++ b/tempest/services/compute/json/floating_ips_client.py
@@ -16,6 +16,7 @@
import json
import urllib
+from tempest.api_schema.compute.v2 import floating_ips as schema
from tempest.common import rest_client
from tempest import config
from tempest import exceptions
@@ -36,6 +37,7 @@
resp, body = self.get(url)
body = json.loads(body)
+ self.validate_response(schema.list_floating_ips, resp, body)
return resp, body['floating_ips']
def get_floating_ip_details(self, floating_ip_id):
diff --git a/tempest/services/compute/json/hosts_client.py b/tempest/services/compute/json/hosts_client.py
index fb45997..0130f27 100644
--- a/tempest/services/compute/json/hosts_client.py
+++ b/tempest/services/compute/json/hosts_client.py
@@ -15,6 +15,7 @@
import json
import urllib
+from tempest.api_schema.compute import hosts as schema
from tempest.common import rest_client
from tempest import config
@@ -36,6 +37,7 @@
resp, body = self.get(url)
body = json.loads(body)
+ self.validate_response(schema.list_hosts, resp, body)
return resp, body['hosts']
def show_host_detail(self, hostname):
diff --git a/tempest/services/compute/json/images_client.py b/tempest/services/compute/json/images_client.py
index 5a79a29..deb9c93 100644
--- a/tempest/services/compute/json/images_client.py
+++ b/tempest/services/compute/json/images_client.py
@@ -16,6 +16,7 @@
import json
import urllib
+from tempest.api_schema.compute.v2 import images as schema
from tempest.common import rest_client
from tempest.common import waiters
from tempest import config
@@ -74,6 +75,7 @@
resp, body = self.get("images/%s" % str(image_id))
self.expected_success(200, resp)
body = json.loads(body)
+ self.validate_response(schema.get_image, resp, body)
return resp, body['image']
def delete_image(self, image_id):
diff --git a/tempest/services/compute/json/limits_client.py b/tempest/services/compute/json/limits_client.py
index 1493718..e503bef 100644
--- a/tempest/services/compute/json/limits_client.py
+++ b/tempest/services/compute/json/limits_client.py
@@ -15,6 +15,7 @@
import json
+from tempest.api_schema.compute.v2 import limits as schema
from tempest.common import rest_client
from tempest import config
@@ -30,11 +31,13 @@
def get_absolute_limits(self):
resp, body = self.get("limits")
body = json.loads(body)
+ self.validate_response(schema.get_limit, resp, body)
return resp, body['limits']['absolute']
def get_specific_absolute_limit(self, absolute_limit):
resp, body = self.get("limits")
body = json.loads(body)
+ self.validate_response(schema.get_limit, resp, body)
if absolute_limit not in body['limits']['absolute']:
return None
else:
diff --git a/tempest/services/compute/json/quotas_client.py b/tempest/services/compute/json/quotas_client.py
index 459ab6d..9346183 100644
--- a/tempest/services/compute/json/quotas_client.py
+++ b/tempest/services/compute/json/quotas_client.py
@@ -15,6 +15,7 @@
import json
+from tempest.api_schema.compute.v2 import quotas as schema
from tempest.common import rest_client
from tempest import config
@@ -27,12 +28,15 @@
super(QuotasClientJSON, self).__init__(auth_provider)
self.service = CONF.compute.catalog_type
- def get_quota_set(self, tenant_id):
+ def get_quota_set(self, tenant_id, user_id=None):
"""List the quota set for a tenant."""
url = 'os-quota-sets/%s' % str(tenant_id)
+ if user_id:
+ url += '?user_id=%s' % str(user_id)
resp, body = self.get(url)
body = json.loads(body)
+ self.validate_response(schema.quota_set, resp, body)
return resp, body['quota_set']
def get_default_quota_set(self, tenant_id):
@@ -41,6 +45,7 @@
url = 'os-quota-sets/%s/defaults' % str(tenant_id)
resp, body = self.get(url)
body = json.loads(body)
+ self.validate_response(schema.quota_set, resp, body)
return resp, body['quota_set']
def update_quota_set(self, tenant_id, force=None,
@@ -100,3 +105,7 @@
body = json.loads(body)
return resp, body['quota_set']
+
+ def delete_quota_set(self, tenant_id):
+ """Delete the tenant's quota set."""
+ return self.delete('os-quota-sets/%s' % str(tenant_id))
diff --git a/tempest/services/compute/json/servers_client.py b/tempest/services/compute/json/servers_client.py
index 623bf42..d6705db 100644
--- a/tempest/services/compute/json/servers_client.py
+++ b/tempest/services/compute/json/servers_client.py
@@ -18,6 +18,7 @@
import time
import urllib
+from tempest.api_schema.compute.v2 import servers as schema
from tempest.common import rest_client
from tempest.common import waiters
from tempest import config
@@ -77,7 +78,12 @@
value = kwargs.get(key)
if value is not None:
post_body[post_param] = value
- post_body = json.dumps({'server': post_body})
+ post_body = {'server': post_body}
+
+ if 'sched_hints' in kwargs:
+ hints = {'os:scheduler_hints': kwargs.get('sched_hints')}
+ post_body = dict(post_body.items() + hints.items())
+ post_body = json.dumps(post_body)
resp, body = self.post('servers', post_body)
body = json.loads(body)
@@ -85,6 +91,7 @@
# with return reservation id set True
if 'reservation_id' in body:
return resp, body
+ self.validate_response(schema.create_server, resp, body)
return resp, body['server']
def update_server(self, server_id, name=None, meta=None, accessIPv4=None,
@@ -382,6 +389,10 @@
"""Un-shelves the provided server."""
return self.action(server_id, 'unshelve', None, **kwargs)
+ def shelve_offload_server(self, server_id, **kwargs):
+ """Shelve-offload the provided server."""
+ return self.action(server_id, 'shelveOffload', None, **kwargs)
+
def get_console_output(self, server_id, length):
return self.action(server_id, 'os-getConsoleOutput', 'output',
length=length)
@@ -436,3 +447,8 @@
def inject_network_info(self, server_id, **kwargs):
"""Inject the Network Info into server"""
return self.action(server_id, 'injectNetworkInfo', None, **kwargs)
+
+ def get_vnc_console(self, server_id, console_type):
+ """Get URL of VNC console."""
+ return self.action(server_id, "os-getVNCConsole",
+ "console", type=console_type)
diff --git a/tempest/services/compute/json/services_client.py b/tempest/services/compute/json/services_client.py
index 1ab25ec..0f7d4cb 100644
--- a/tempest/services/compute/json/services_client.py
+++ b/tempest/services/compute/json/services_client.py
@@ -17,6 +17,7 @@
import json
import urllib
+from tempest.api_schema.compute import services as schema
from tempest.common import rest_client
from tempest import config
@@ -36,6 +37,7 @@
resp, body = self.get(url)
body = json.loads(body)
+ self.validate_response(schema.list_services, resp, body)
return resp, body['services']
def enable_service(self, host_name, binary):
diff --git a/tempest/services/compute/json/volumes_extensions_client.py b/tempest/services/compute/json/volumes_extensions_client.py
index 5ef11ed..451dbac 100644
--- a/tempest/services/compute/json/volumes_extensions_client.py
+++ b/tempest/services/compute/json/volumes_extensions_client.py
@@ -17,6 +17,7 @@
import time
import urllib
+from tempest.api_schema.compute.v2 import volumes as schema
from tempest.common import rest_client
from tempest import config
from tempest import exceptions
@@ -58,6 +59,7 @@
url = "os-volumes/%s" % str(volume_id)
resp, body = self.get(url)
body = json.loads(body)
+ self.validate_response(schema.get_volume, resp, body)
return resp, body['volume']
def create_volume(self, size, **kwargs):
diff --git a/tempest/services/compute/v3/json/agents_client.py b/tempest/services/compute/v3/json/agents_client.py
new file mode 100644
index 0000000..6893af2
--- /dev/null
+++ b/tempest/services/compute/v3/json/agents_client.py
@@ -0,0 +1,52 @@
+# Copyright 2014 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 import rest_client
+from tempest import config
+
+CONF = config.CONF
+
+
+class AgentsV3ClientJSON(rest_client.RestClient):
+
+ def __init__(self, auth_provider):
+ super(AgentsV3ClientJSON, self).__init__(auth_provider)
+ self.service = CONF.compute.catalog_v3_type
+
+ def list_agents(self, params=None):
+ """List all agent builds."""
+ url = 'os-agents'
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+ resp, body = self.get(url)
+ return resp, self._parse_resp(body)
+
+ def create_agent(self, **kwargs):
+ """Create an agent build."""
+ post_body = json.dumps({'agent': kwargs})
+ resp, body = self.post('os-agents', post_body)
+ return resp, self._parse_resp(body)
+
+ def delete_agent(self, agent_id):
+ """Delete an existing agent build."""
+ return self.delete('os-agents/%s' % str(agent_id))
+
+ def update_agent(self, agent_id, **kwargs):
+ """Update an agent build."""
+ put_body = json.dumps({'agent': kwargs})
+ resp, body = self.put('os-agents/%s' % str(agent_id), put_body)
+ return resp, self._parse_resp(body)
diff --git a/tempest/services/compute/v3/json/hosts_client.py b/tempest/services/compute/v3/json/hosts_client.py
index e27c7c6..bcb9d36 100644
--- a/tempest/services/compute/v3/json/hosts_client.py
+++ b/tempest/services/compute/v3/json/hosts_client.py
@@ -15,6 +15,7 @@
import json
import urllib
+from tempest.api_schema.compute import hosts as schema
from tempest.common import rest_client
from tempest import config
@@ -36,6 +37,7 @@
resp, body = self.get(url)
body = json.loads(body)
+ self.validate_response(schema.list_hosts, resp, body)
return resp, body['hosts']
def show_host_detail(self, hostname):
diff --git a/tempest/services/compute/v3/json/quotas_client.py b/tempest/services/compute/v3/json/quotas_client.py
index 32e31a3..a8507c4 100644
--- a/tempest/services/compute/v3/json/quotas_client.py
+++ b/tempest/services/compute/v3/json/quotas_client.py
@@ -15,6 +15,7 @@
import json
+from tempest.api_schema.compute.v3 import quotas as schema
from tempest.common import rest_client
from tempest import config
@@ -27,12 +28,15 @@
super(QuotasV3ClientJSON, self).__init__(auth_provider)
self.service = CONF.compute.catalog_v3_type
- def get_quota_set(self, tenant_id):
+ def get_quota_set(self, tenant_id, user_id=None):
"""List the quota set for a tenant."""
url = 'os-quota-sets/%s' % str(tenant_id)
+ if user_id:
+ url += '?user_id=%s' % str(user_id)
resp, body = self.get(url)
body = json.loads(body)
+ self.validate_response(schema.quota_set, resp, body)
return resp, body['quota_set']
def get_quota_set_detail(self, tenant_id):
@@ -49,6 +53,7 @@
url = 'os-quota-sets/%s/defaults' % str(tenant_id)
resp, body = self.get(url)
body = json.loads(body)
+ self.validate_response(schema.quota_set, resp, body)
return resp, body['quota_set']
def update_quota_set(self, tenant_id, force=None,
@@ -95,4 +100,9 @@
resp, body = self.put('os-quota-sets/%s' % str(tenant_id), post_body)
body = json.loads(body)
+ self.validate_response(schema.quota_set, resp, body)
return resp, body['quota_set']
+
+ def delete_quota_set(self, tenant_id):
+ """Delete the tenant's quota set."""
+ return self.delete('os-quota-sets/%s' % str(tenant_id))
diff --git a/tempest/services/compute/v3/json/servers_client.py b/tempest/services/compute/v3/json/servers_client.py
index 256a730..6f492d0 100644
--- a/tempest/services/compute/v3/json/servers_client.py
+++ b/tempest/services/compute/v3/json/servers_client.py
@@ -19,6 +19,7 @@
import time
import urllib
+from tempest.api_schema.compute.v3 import servers as schema
from tempest.common import rest_client
from tempest.common import waiters
from tempest import config
@@ -91,6 +92,7 @@
# with return reservation id set True
if 'servers_reservation' in body:
return resp, body['servers_reservation']
+ self.validate_response(schema.create_server, resp, body)
return resp, body['server']
def update_server(self, server_id, name=None, meta=None, access_ip_v4=None,
@@ -384,6 +386,10 @@
"""Un-shelves the provided server."""
return self.action(server_id, 'unshelve', None, **kwargs)
+ def shelve_offload_server(self, server_id, **kwargs):
+ """Shelve-offload the provided server."""
+ return self.action(server_id, 'shelve_offload', None, **kwargs)
+
def get_console_output(self, server_id, length):
return self.action(server_id, 'get_console_output', 'output',
length=length)
diff --git a/tempest/services/compute/v3/json/services_client.py b/tempest/services/compute/v3/json/services_client.py
index b4e65a0..88c4d16 100644
--- a/tempest/services/compute/v3/json/services_client.py
+++ b/tempest/services/compute/v3/json/services_client.py
@@ -17,6 +17,7 @@
import json
import urllib
+from tempest.api_schema.compute import services as schema
from tempest.common import rest_client
from tempest import config
@@ -36,6 +37,7 @@
resp, body = self.get(url)
body = json.loads(body)
+ self.validate_response(schema.list_services, resp, body)
return resp, body['services']
def enable_service(self, host_name, binary):
diff --git a/tempest/services/compute/xml/common.py b/tempest/services/compute/xml/common.py
index b29b932..b1bf789 100644
--- a/tempest/services/compute/xml/common.py
+++ b/tempest/services/compute/xml/common.py
@@ -19,6 +19,7 @@
XMLNS_V3 = "http://docs.openstack.org/compute/api/v1.1"
NEUTRON_NAMESPACES = {
+ 'binding': "http://docs.openstack.org/ext/binding/api/v1.0",
'router': "http://docs.openstack.org/ext/neutron/router/api/v1.0",
'provider': 'http://docs.openstack.org/ext/provider/api/v1.0',
}
diff --git a/tempest/services/compute/xml/quotas_client.py b/tempest/services/compute/xml/quotas_client.py
index b8b759f..911c476 100644
--- a/tempest/services/compute/xml/quotas_client.py
+++ b/tempest/services/compute/xml/quotas_client.py
@@ -44,10 +44,12 @@
return quota
- def get_quota_set(self, tenant_id):
+ def get_quota_set(self, tenant_id, user_id=None):
"""List the quota set for a tenant."""
url = 'os-quota-sets/%s' % str(tenant_id)
+ if user_id:
+ url += '?user_id=%s' % str(user_id)
resp, body = self.get(url)
body = xml_to_json(etree.fromstring(body))
body = self._format_quota(body)
@@ -121,3 +123,7 @@
body = xml_to_json(etree.fromstring(body))
body = self._format_quota(body)
return resp, body
+
+ def delete_quota_set(self, tenant_id):
+ """Delete the tenant's quota set."""
+ return self.delete('os-quota-sets/%s' % str(tenant_id))
diff --git a/tempest/services/compute/xml/servers_client.py b/tempest/services/compute/xml/servers_client.py
index cd2cb06..7a2a071 100644
--- a/tempest/services/compute/xml/servers_client.py
+++ b/tempest/services/compute/xml/servers_client.py
@@ -227,6 +227,10 @@
"""Un-shelves the provided server."""
return self.action(server_id, 'unshelve', None, **kwargs)
+ def shelve_offload_server(self, server_id, **kwargs):
+ """Shelve-offload the provided server."""
+ return self.action(server_id, 'shelveOffload', 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)
@@ -356,6 +360,15 @@
temp.append(Text(k['contents']))
personality.append(temp)
+ if 'sched_hints' in kwargs:
+ sched_hints = kwargs.get('sched_hints')
+ hints = Element("os:scheduler_hints")
+ hints.add_attr('xmlns:os', XMLNS_11)
+ for attr in sched_hints:
+ p1 = Element(attr)
+ p1.append(sched_hints[attr])
+ hints.append(p1)
+ server.append(hints)
resp, body = self.post('servers', str(Document(server)))
server = self._parse_server(etree.fromstring(body))
return resp, server
@@ -641,3 +654,8 @@
def inject_network_info(self, server_id, **kwargs):
"""Inject the Network Info into server"""
return self.action(server_id, 'injectNetworkInfo', None, **kwargs)
+
+ def get_vnc_console(self, server_id, console_type):
+ """Get URL of VNC console."""
+ return self.action(server_id, "os-getVNCConsole",
+ "console", type=console_type)
diff --git a/tempest/services/database/json/flavors_client.py b/tempest/services/database/json/flavors_client.py
index 1a8a4c1..2ec0405 100644
--- a/tempest/services/database/json/flavors_client.py
+++ b/tempest/services/database/json/flavors_client.py
@@ -13,9 +13,10 @@
# License for the specific language governing permissions and limitations
# under the License.
+import urllib
+
from tempest.common import rest_client
from tempest import config
-import urllib
CONF = config.CONF
diff --git a/tempest/services/identity/json/identity_client.py b/tempest/services/identity/json/identity_client.py
index 349a9e9..99b4036 100644
--- a/tempest/services/identity/json/identity_client.py
+++ b/tempest/services/identity/json/identity_client.py
@@ -134,9 +134,10 @@
post_body = {
'name': name,
'password': password,
- 'tenantId': tenant_id,
'email': email
}
+ if tenant_id is not None:
+ post_body['tenantId'] = tenant_id
if kwargs.get('enabled') is not None:
post_body['enabled'] = kwargs.get('enabled')
post_body = json.dumps({'user': post_body})
@@ -172,6 +173,11 @@
resp, body = self.put('users/%s/enabled' % user_id, put_body)
return resp, self._parse_resp(body)
+ def get_token(self, token_id):
+ """Get token details."""
+ resp, body = self.get("tokens/%s" % token_id)
+ return resp, self._parse_resp(body)
+
def delete_token(self, token_id):
"""Delete a token."""
return self.delete("tokens/%s" % token_id)
@@ -228,16 +234,36 @@
self.auth_url = auth_url
- def auth(self, user, password, tenant):
+ def auth(self, user, password, tenant=None):
creds = {
'auth': {
'passwordCredentials': {
'username': user,
'password': password,
},
- 'tenantName': tenant,
}
}
+
+ if tenant:
+ creds['auth']['tenantName'] = tenant
+
+ body = json.dumps(creds)
+ resp, body = self.post(self.auth_url, body=body)
+
+ return resp, body['access']
+
+ def auth_token(self, token_id, tenant=None):
+ creds = {
+ 'auth': {
+ 'token': {
+ 'id': token_id,
+ },
+ }
+ }
+
+ if tenant:
+ creds['auth']['tenantName'] = tenant
+
body = json.dumps(creds)
resp, body = self.post(self.auth_url, body=body)
diff --git a/tempest/services/identity/v3/json/endpoints_client.py b/tempest/services/identity/v3/json/endpoints_client.py
index c3c1e15..f7a894b 100644
--- a/tempest/services/identity/v3/json/endpoints_client.py
+++ b/tempest/services/identity/v3/json/endpoints_client.py
@@ -36,9 +36,17 @@
return resp, body['endpoints']
def create_endpoint(self, service_id, interface, url, **kwargs):
- """Create endpoint."""
+ """Create endpoint.
+
+ Normally this function wouldn't allow setting values that are not
+ allowed for 'enabled'. Use `force_enabled` to set a non-boolean.
+
+ """
region = kwargs.get('region', None)
- enabled = kwargs.get('enabled', None)
+ if 'force_enabled' in kwargs:
+ enabled = kwargs.get('force_enabled', None)
+ else:
+ enabled = kwargs.get('enabled', None)
post_body = {
'service_id': service_id,
'interface': interface,
@@ -52,8 +60,13 @@
return resp, body['endpoint']
def update_endpoint(self, endpoint_id, service_id=None, interface=None,
- url=None, region=None, enabled=None):
- """Updates an endpoint with given parameters."""
+ url=None, region=None, enabled=None, **kwargs):
+ """Updates an endpoint with given parameters.
+
+ Normally this function wouldn't allow setting values that are not
+ allowed for 'enabled'. Use `force_enabled` to set a non-boolean.
+
+ """
post_body = {}
if service_id is not None:
post_body['service_id'] = service_id
@@ -63,7 +76,9 @@
post_body['url'] = url
if region is not None:
post_body['region'] = region
- if enabled is not None:
+ if 'force_enabled' in kwargs:
+ post_body['enabled'] = kwargs['force_enabled']
+ elif enabled is not None:
post_body['enabled'] = enabled
post_body = json.dumps({'endpoint': post_body})
resp, body = self.patch('endpoints/%s' % endpoint_id, post_body)
diff --git a/tempest/services/identity/v3/json/identity_client.py b/tempest/services/identity/v3/json/identity_client.py
index 65f3355..285feb3 100644
--- a/tempest/services/identity/v3/json/identity_client.py
+++ b/tempest/services/identity/v3/json/identity_client.py
@@ -297,6 +297,12 @@
body = json.loads(body)
return resp, body['users']
+ def list_user_groups(self, user_id):
+ """Lists groups which a user belongs to."""
+ resp, body = self.get('users/%s/groups' % user_id)
+ body = json.loads(body)
+ return resp, body['groups']
+
def delete_group_user(self, group_id, user_id):
"""Delete user in group."""
resp, body = self.delete('groups/%s/users/%s' % (group_id, user_id))
diff --git a/tempest/services/identity/v3/xml/endpoints_client.py b/tempest/services/identity/v3/xml/endpoints_client.py
index cc9aa65..a1f9811 100644
--- a/tempest/services/identity/v3/xml/endpoints_client.py
+++ b/tempest/services/identity/v3/xml/endpoints_client.py
@@ -62,11 +62,19 @@
return resp, body
def create_endpoint(self, service_id, interface, url, **kwargs):
- """Create endpoint."""
+ """Create endpoint.
+
+ Normally this function wouldn't allow setting values that are not
+ allowed for 'enabled'. Use `force_enabled` to set a non-boolean.
+
+ """
region = kwargs.get('region', None)
- enabled = kwargs.get('enabled', None)
- if enabled is not None:
- enabled = str(enabled).lower()
+ if 'force_enabled' in kwargs:
+ enabled = kwargs['force_enabled']
+ else:
+ enabled = kwargs.get('enabled', None)
+ if enabled is not None:
+ enabled = str(enabled).lower()
create_endpoint = common.Element("endpoint",
xmlns=XMLNS,
service_id=service_id,
@@ -79,8 +87,13 @@
return resp, body
def update_endpoint(self, endpoint_id, service_id=None, interface=None,
- url=None, region=None, enabled=None):
- """Updates an endpoint with given parameters."""
+ url=None, region=None, enabled=None, **kwargs):
+ """Updates an endpoint with given parameters.
+
+ Normally this function wouldn't allow setting values that are not
+ allowed for 'enabled'. Use `force_enabled` to set a non-boolean.
+
+ """
doc = common.Document()
endpoint = common.Element("endpoint")
doc.append(endpoint)
@@ -93,8 +106,12 @@
endpoint.add_attr("url", url)
if region:
endpoint.add_attr("region", region)
- if enabled is not None:
+
+ if 'force_enabled' in kwargs:
+ endpoint.add_attr("enabled", kwargs['force_enabled'])
+ elif enabled is not None:
endpoint.add_attr("enabled", str(enabled).lower())
+
resp, body = self.patch('endpoints/%s' % str(endpoint_id), str(doc))
body = self._parse_body(etree.fromstring(body))
return resp, body
diff --git a/tempest/services/identity/v3/xml/identity_client.py b/tempest/services/identity/v3/xml/identity_client.py
index 6ff6d56..d6c5bc1 100644
--- a/tempest/services/identity/v3/xml/identity_client.py
+++ b/tempest/services/identity/v3/xml/identity_client.py
@@ -52,6 +52,14 @@
array.append(common.xml_to_json(child))
return array
+ def _parse_groups(self, node):
+ array = []
+ for child in node.getchildren():
+ tag_list = child.tag.split('}', 1)
+ if tag_list[1] == "group":
+ array.append(common.xml_to_json(child))
+ return array
+
def _parse_group_users(self, node):
array = []
for child in node.getchildren():
@@ -342,6 +350,12 @@
body = self._parse_group_users(etree.fromstring(body))
return resp, body
+ def list_user_groups(self, user_id):
+ """Lists the groups which a user belongs to."""
+ resp, body = self.get('users/%s/groups' % user_id)
+ body = self._parse_groups(etree.fromstring(body))
+ return resp, body
+
def delete_group_user(self, group_id, user_id):
"""Delete user in group."""
resp, body = self.delete('groups/%s/users/%s' % (group_id, user_id))
diff --git a/tempest/services/identity/xml/identity_client.py b/tempest/services/identity/xml/identity_client.py
index 50403fb..c5bf310 100644
--- a/tempest/services/identity/xml/identity_client.py
+++ b/tempest/services/identity/xml/identity_client.py
@@ -75,8 +75,9 @@
xmlns=XMLNS,
name=name,
password=password,
- tenantId=tenant_id,
email=email)
+ if tenant_id:
+ create_user.add_attr('tenantId', tenant_id)
if 'enabled' in kwargs:
create_user.add_attr('enabled', str(kwargs['enabled']).lower())
@@ -116,11 +117,24 @@
class TokenClientXML(identity_client.TokenClientJSON):
TYPE = "xml"
- def auth(self, user, password, tenant):
- passwordCreds = xml.Element("passwordCredentials",
+ def auth(self, user, password, tenant=None):
+ passwordCreds = xml.Element('passwordCredentials',
username=user,
password=password)
- auth = xml.Element("auth", tenantName=tenant)
+ auth_kwargs = {}
+ if tenant:
+ auth_kwargs['tenantName'] = tenant
+ auth = xml.Element('auth', **auth_kwargs)
auth.append(passwordCreds)
resp, body = self.post(self.auth_url, body=str(xml.Document(auth)))
return resp, body['access']
+
+ def auth_token(self, token_id, tenant=None):
+ tokenCreds = xml.Element('token', id=token_id)
+ auth_kwargs = {}
+ if tenant:
+ auth_kwargs['tenantName'] = tenant
+ auth = xml.Element('auth', **auth_kwargs)
+ auth.append(tokenCreds)
+ resp, body = self.post(self.auth_url, body=str(xml.Document(auth)))
+ return resp, body['access']
diff --git a/tempest/services/image/v1/json/image_client.py b/tempest/services/image/v1/json/image_client.py
index 932fa14..e22cd9c 100644
--- a/tempest/services/image/v1/json/image_client.py
+++ b/tempest/services/image/v1/json/image_client.py
@@ -21,7 +21,7 @@
import urllib
from tempest.common import glance_http
-from tempest.common.rest_client import RestClient
+from tempest.common import rest_client
from tempest import config
from tempest import exceptions
from tempest.openstack.common import log as logging
@@ -31,7 +31,7 @@
LOG = logging.getLogger(__name__)
-class ImageClientJSON(RestClient):
+class ImageClientJSON(rest_client.RestClient):
def __init__(self, auth_provider):
super(ImageClientJSON, self).__init__(auth_provider)
diff --git a/tempest/services/network/json/network_client.py b/tempest/services/network/json/network_client.py
index 366ccee..a804e8e 100644
--- a/tempest/services/network/json/network_client.py
+++ b/tempest/services/network/json/network_client.py
@@ -12,7 +12,7 @@
import json
-from tempest.common.rest_client import RestClient
+from tempest.common import rest_client
from tempest.services.network import network_client_base
@@ -32,7 +32,7 @@
"""
def get_rest_client(self, auth_provider):
- return RestClient(auth_provider)
+ return rest_client.RestClient(auth_provider)
def deserialize_single(self, body):
return json.loads(body)
@@ -163,32 +163,6 @@
body = json.loads(body)
return resp, body
- def create_member(self, address, protocol_port, pool_id):
- post_body = {
- "member": {
- "protocol_port": protocol_port,
- "pool_id": pool_id,
- "address": address
- }
- }
- body = json.dumps(post_body)
- uri = '%s/lb/members' % (self.uri_prefix)
- resp, body = self.post(uri, body)
- body = json.loads(body)
- return resp, body
-
- def update_member(self, admin_state_up, member_id):
- put_body = {
- "member": {
- "admin_state_up": admin_state_up
- }
- }
- body = json.dumps(put_body)
- uri = '%s/lb/members/%s' % (self.uri_prefix, member_id)
- resp, body = self.put(uri, body)
- body = json.loads(body)
- return resp, body
-
def associate_health_monitor_with_pool(self, health_monitor_id,
pool_id):
post_body = {
@@ -280,6 +254,20 @@
body = json.loads(body)
return resp, body
+ def add_router_to_l3_agent(self, agent_id, router_id):
+ uri = '%s/agents/%s/l3-routers' % (self.uri_prefix, agent_id)
+ post_body = {"router_id": router_id}
+ body = json.dumps(post_body)
+ resp, body = self.post(uri, body)
+ body = json.loads(body)
+ return resp, body
+
+ def remove_router_from_l3_agent(self, agent_id, router_id):
+ uri = '%s/agents/%s/l3-routers/%s' % (
+ self.uri_prefix, agent_id, router_id)
+ resp, body = self.delete(uri)
+ return resp, body
+
def list_dhcp_agent_hosting_network(self, network_id):
uri = '%s/networks/%s/dhcp-agents' % (self.uri_prefix, network_id)
resp, body = self.get(uri)
diff --git a/tempest/services/network/network_client_base.py b/tempest/services/network/network_client_base.py
index f1bf548..41a7aa4 100644
--- a/tempest/services/network/network_client_base.py
+++ b/tempest/services/network/network_client_base.py
@@ -10,9 +10,11 @@
# License for the specific language governing permissions and limitations
# under the License.
+import time
import urllib
from tempest import config
+from tempest import exceptions
CONF = config.CONF
@@ -54,6 +56,8 @@
self.rest_client.service = CONF.network.catalog_type
self.version = '2.0'
self.uri_prefix = "v%s" % (self.version)
+ self.build_timeout = CONF.network.build_timeout
+ self.build_interval = CONF.network.build_interval
def get_rest_client(self, auth_provider):
raise NotImplementedError
@@ -189,3 +193,23 @@
resp, body = self.post(uri, body)
body = {'ports': self.deserialize_list(body)}
return resp, body
+
+ def wait_for_resource_deletion(self, resource_type, id):
+ """Waits for a resource to be deleted."""
+ start_time = int(time.time())
+ while True:
+ if self.is_resource_deleted(resource_type, id):
+ return
+ if int(time.time()) - start_time >= self.build_timeout:
+ raise exceptions.TimeoutException
+ time.sleep(self.build_interval)
+
+ def is_resource_deleted(self, resource_type, id):
+ method = 'show_' + resource_type
+ try:
+ getattr(self, method)(id)
+ except AttributeError:
+ raise Exception("Unknown resource type %s " % resource_type)
+ except exceptions.NotFound:
+ return True
+ return False
diff --git a/tempest/services/network/xml/network_client.py b/tempest/services/network/xml/network_client.py
index 8152d71..2a5083c 100644
--- a/tempest/services/network/xml/network_client.py
+++ b/tempest/services/network/xml/network_client.py
@@ -23,7 +23,8 @@
# list of plurals used for xml serialization
PLURALS = ['dns_nameservers', 'host_routes', 'allocation_pools',
- 'fixed_ips', 'extensions', 'extra_dhcp_opts']
+ 'fixed_ips', 'extensions', 'extra_dhcp_opts', 'pools',
+ 'health_monitors', 'vips']
def get_rest_client(self, auth_provider):
rc = rest_client.RestClient(auth_provider)
@@ -92,28 +93,6 @@
root.add_attr('xmlns:%s' % element,
common.NEUTRON_NAMESPACES[element])
- def create_member(self, address, protocol_port, pool_id):
- uri = '%s/lb/members' % (self.uri_prefix)
- post_body = common.Element("member")
- p1 = common.Element("address", address)
- p2 = common.Element("protocol_port", protocol_port)
- p3 = common.Element("pool_id", pool_id)
- post_body.append(p1)
- post_body.append(p2)
- post_body.append(p3)
- resp, body = self.post(uri, str(common.Document(post_body)))
- body = _root_tag_fetcher_and_xml_to_json_parse(body)
- return resp, body
-
- def update_member(self, admin_state_up, member_id):
- uri = '%s/lb/members/%s' % (self.uri_prefix, str(member_id))
- put_body = common.Element("member")
- p2 = common.Element("admin_state_up", admin_state_up)
- put_body.append(p2)
- resp, body = self.put(uri, str(common.Document(put_body)))
- body = _root_tag_fetcher_and_xml_to_json_parse(body)
- return resp, body
-
def associate_health_monitor_with_pool(self, health_monitor_id,
pool_id):
uri = '%s/lb/pools/%s/health_monitors' % (self.uri_prefix,
@@ -248,15 +227,30 @@
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)
- body = _root_tag_fetcher_and_xml_to_json_parse(body)
+ routers = common.parse_array(etree.fromstring(body))
+ body = {'routers': routers}
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)
+ agents = common.parse_array(etree.fromstring(body))
+ body = {'agents': agents}
+ return resp, body
+
+ def add_router_to_l3_agent(self, agent_id, router_id):
+ uri = '%s/agents/%s/l3-routers' % (self.uri_prefix, agent_id)
+ router = (common.Element("router_id", router_id))
+ resp, body = self.post(uri, str(common.Document(router)))
body = _root_tag_fetcher_and_xml_to_json_parse(body)
return resp, body
+ def remove_router_from_l3_agent(self, agent_id, router_id):
+ uri = '%s/agents/%s/l3-routers/%s' % (
+ self.uri_prefix, agent_id, router_id)
+ resp, body = self.delete(uri)
+ return resp, body
+
def list_dhcp_agent_hosting_network(self, network_id):
uri = '%s/networks/%s/dhcp-agents' % (self.uri_prefix, network_id)
resp, body = self.get(uri)
diff --git a/tempest/services/object_storage/account_client.py b/tempest/services/object_storage/account_client.py
index be314cc..7c3fa85 100644
--- a/tempest/services/object_storage/account_client.py
+++ b/tempest/services/object_storage/account_client.py
@@ -17,7 +17,7 @@
import urllib
from tempest.common import http
-from tempest.common.rest_client import RestClient
+from tempest.common import rest_client
from tempest import config
from tempest import exceptions
from xml.etree import ElementTree as etree
@@ -25,7 +25,7 @@
CONF = config.CONF
-class AccountClient(RestClient):
+class AccountClient(rest_client.RestClient):
def __init__(self, auth_provider):
super(AccountClient, self).__init__(auth_provider)
self.service = CONF.object_storage.catalog_type
@@ -151,14 +151,14 @@
return resp, body
-class AccountClientCustomizedHeader(RestClient):
+class AccountClientCustomizedHeader(rest_client.RestClient):
# TODO(andreaf) This class is now redundant, to be removed in next patch
def __init__(self, auth_provider):
super(AccountClientCustomizedHeader, self).__init__(
auth_provider)
- # Overwrites json-specific header encoding in RestClient
+ # Overwrites json-specific header encoding in rest_client.RestClient
self.service = CONF.object_storage.catalog_type
self.format = 'json'
diff --git a/tempest/services/object_storage/container_client.py b/tempest/services/object_storage/container_client.py
index f224407..546b557 100644
--- a/tempest/services/object_storage/container_client.py
+++ b/tempest/services/object_storage/container_client.py
@@ -16,18 +16,18 @@
import json
import urllib
-from tempest.common.rest_client import RestClient
+from tempest.common import rest_client
from tempest import config
from xml.etree import ElementTree as etree
CONF = config.CONF
-class ContainerClient(RestClient):
+class ContainerClient(rest_client.RestClient):
def __init__(self, auth_provider):
super(ContainerClient, self).__init__(auth_provider)
- # Overwrites json-specific header encoding in RestClient
+ # Overwrites json-specific header encoding in rest_client.RestClient
self.headers = {}
self.service = CONF.object_storage.catalog_type
self.format = 'json'
diff --git a/tempest/services/object_storage/object_client.py b/tempest/services/object_storage/object_client.py
index 09e484e..77d29a5 100644
--- a/tempest/services/object_storage/object_client.py
+++ b/tempest/services/object_storage/object_client.py
@@ -16,14 +16,14 @@
import urllib
from tempest.common import http
-from tempest.common.rest_client import RestClient
+from tempest.common import rest_client
from tempest import config
from tempest import exceptions
CONF = config.CONF
-class ObjectClient(RestClient):
+class ObjectClient(rest_client.RestClient):
def __init__(self, auth_provider):
super(ObjectClient, self).__init__(auth_provider)
@@ -135,14 +135,14 @@
return resp, body
-class ObjectClientCustomizedHeader(RestClient):
+class ObjectClientCustomizedHeader(rest_client.RestClient):
# TODO(andreaf) This class is now redundant, to be removed in next patch
def __init__(self, auth_provider):
super(ObjectClientCustomizedHeader, self).__init__(
auth_provider)
- # Overwrites json-specific header encoding in RestClient
+ # Overwrites json-specific header encoding in rest_client.RestClient
self.service = CONF.object_storage.catalog_type
self.format = 'json'
diff --git a/tempest/services/queuing/__init__.py b/tempest/services/queuing/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/services/queuing/__init__.py
diff --git a/tempest/services/queuing/json/__init__.py b/tempest/services/queuing/json/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/services/queuing/json/__init__.py
diff --git a/tempest/services/queuing/json/queuing_client.py b/tempest/services/queuing/json/queuing_client.py
new file mode 100644
index 0000000..4a0c495
--- /dev/null
+++ b/tempest/services/queuing/json/queuing_client.py
@@ -0,0 +1,58 @@
+# Copyright (c) 2014 Rackspace, Inc.
+#
+# 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 import rest_client
+from tempest import config
+
+CONF = config.CONF
+
+
+class QueuingClientJSON(rest_client.RestClient):
+
+ def __init__(self, auth_provider):
+ super(QueuingClientJSON, self).__init__(auth_provider)
+ self.service = CONF.queuing.catalog_type
+ self.version = '1'
+ self.uri_prefix = 'v{0}'.format(self.version)
+
+ def list_queues(self):
+ uri = '{0}/queues'.format(self.uri_prefix)
+ resp, body = self.get(uri)
+ body = json.loads(body)
+ return resp, body
+
+ def create_queue(self, queue_name):
+ uri = '{0}/queues/{1}'.format(self.uri_prefix, queue_name)
+ resp, body = self.put(uri, body=None)
+ return resp, body
+
+ def get_queue(self, queue_name):
+ uri = '{0}/queues/{1}'.format(self.uri_prefix, queue_name)
+ resp, body = self.get(uri)
+ body = json.loads(body)
+ return resp, body
+
+ def head_queue(self, queue_name):
+ uri = '{0}/queues/{1}'.format(self.uri_prefix, queue_name)
+ resp, body = self.head(uri)
+ body = json.loads(body)
+ return resp, body
+
+ def delete_queue(self, queue_name):
+ uri = '{0}/queues/{1}'.format(self.uri_prefix, queue_name)
+ resp = self.delete(uri)
+ return resp
diff --git a/tempest/services/telemetry/json/telemetry_client.py b/tempest/services/telemetry/json/telemetry_client.py
index e666475..996aceb 100644
--- a/tempest/services/telemetry/json/telemetry_client.py
+++ b/tempest/services/telemetry/json/telemetry_client.py
@@ -13,7 +13,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest.common.rest_client import RestClient
+from tempest.common import rest_client
from tempest.openstack.common import jsonutils as json
import tempest.services.telemetry.telemetry_client_base as client
@@ -21,7 +21,7 @@
class TelemetryClientJSON(client.TelemetryClientBase):
def get_rest_client(self, auth_provider):
- return RestClient(auth_provider)
+ return rest_client.RestClient(auth_provider)
def deserialize(self, body):
return json.loads(body.replace("\n", ""))
diff --git a/tempest/services/telemetry/xml/telemetry_client.py b/tempest/services/telemetry/xml/telemetry_client.py
index 165f29a..673f98e 100644
--- a/tempest/services/telemetry/xml/telemetry_client.py
+++ b/tempest/services/telemetry/xml/telemetry_client.py
@@ -16,8 +16,7 @@
from lxml import etree
from tempest.common import rest_client
-from tempest.services.compute.xml.common import Document
-from tempest.services.compute.xml.common import xml_to_json
+from tempest.services.compute.xml import common
import tempest.services.telemetry.telemetry_client_base as client
@@ -32,11 +31,11 @@
def _parse_array(self, body):
array = []
for child in body.getchildren():
- array.append(xml_to_json(child))
+ array.append(common.xml_to_json(child))
return array
def serialize(self, body):
- return str(Document(body))
+ return str(common.Document(body))
def deserialize(self, body):
return self._parse_array(etree.fromstring(body))
diff --git a/tempest/services/volume/json/admin/volume_hosts_client.py b/tempest/services/volume/json/admin/volume_hosts_client.py
index 6efb258..84e4705 100644
--- a/tempest/services/volume/json/admin/volume_hosts_client.py
+++ b/tempest/services/volume/json/admin/volume_hosts_client.py
@@ -16,13 +16,13 @@
import json
import urllib
-from tempest.common.rest_client import RestClient
+from tempest.common import rest_client
from tempest import config
CONF = config.CONF
-class VolumeHostsClientJSON(RestClient):
+class VolumeHostsClientJSON(rest_client.RestClient):
"""
Client class to send CRUD Volume Hosts API requests to a Cinder endpoint
"""
diff --git a/tempest/services/volume/json/admin/volume_quotas_client.py b/tempest/services/volume/json/admin/volume_quotas_client.py
new file mode 100644
index 0000000..ea9c92e
--- /dev/null
+++ b/tempest/services/volume/json/admin/volume_quotas_client.py
@@ -0,0 +1,79 @@
+# Copyright (C) 2014 eNovance SAS <licensing@enovance.com>
+#
+# Author: Sylvain Baubeau <sylvain.baubeau@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.
+
+import urllib
+
+from tempest.openstack.common import jsonutils
+
+from tempest.common import rest_client
+from tempest import config
+
+CONF = config.CONF
+
+
+class VolumeQuotasClientJSON(rest_client.RestClient):
+ """
+ Client class to send CRUD Volume Quotas API requests to a Cinder endpoint
+ """
+
+ TYPE = "json"
+
+ def __init__(self, auth_provider):
+ super(VolumeQuotasClientJSON, self).__init__(auth_provider)
+
+ self.service = CONF.volume.catalog_type
+ self.build_interval = CONF.volume.build_interval
+ self.build_timeout = CONF.volume.build_timeout
+
+ def get_default_quota_set(self, tenant_id):
+ """List the default volume quota set for a tenant."""
+
+ url = 'os-quota-sets/%s/defaults' % tenant_id
+ resp, body = self.get(url)
+ return resp, self._parse_resp(body)
+
+ def get_quota_set(self, tenant_id, params=None):
+ """List the quota set for a tenant."""
+
+ url = 'os-quota-sets/%s' % tenant_id
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+
+ resp, body = self.get(url)
+ return resp, self._parse_resp(body)
+
+ def get_quota_usage(self, tenant_id):
+ """List the quota set for a tenant."""
+
+ resp, body = self.get_quota_set(tenant_id, params={'usage': True})
+ return resp, body
+
+ def update_quota_set(self, tenant_id, gigabytes=None, volumes=None,
+ snapshots=None):
+ post_body = {}
+
+ if gigabytes is not None:
+ post_body['gigabytes'] = gigabytes
+
+ if volumes is not None:
+ post_body['volumes'] = volumes
+
+ if snapshots is not None:
+ post_body['snapshots'] = snapshots
+
+ post_body = jsonutils.dumps({'quota_set': post_body})
+ resp, body = self.put('os-quota-sets/%s' % tenant_id, post_body)
+ return resp, self._parse_resp(body)
diff --git a/tempest/services/volume/json/admin/volume_types_client.py b/tempest/services/volume/json/admin/volume_types_client.py
index 0d50524..c9c0582 100644
--- a/tempest/services/volume/json/admin/volume_types_client.py
+++ b/tempest/services/volume/json/admin/volume_types_client.py
@@ -16,13 +16,13 @@
import json
import urllib
-from tempest.common.rest_client import RestClient
+from tempest.common import rest_client
from tempest import config
CONF = config.CONF
-class VolumeTypesClientJSON(RestClient):
+class VolumeTypesClientJSON(rest_client.RestClient):
"""
Client class to send CRUD Volume Types API requests to a Cinder endpoint
"""
@@ -122,3 +122,31 @@
resp, body = self.put(url, put_body)
body = json.loads(body)
return resp, body
+
+ def get_encryption_type(self, vol_type_id):
+ """
+ Get the volume encryption type for the specified volume type.
+ vol_type_id: Id of volume_type.
+ """
+ url = "/types/%s/encryption" % str(vol_type_id)
+ resp, body = self.get(url)
+ body = json.loads(body)
+ return resp, body
+
+ def create_encryption_type(self, vol_type_id, **kwargs):
+ """
+ Create a new encryption type for the specified volume type.
+
+ vol_type_id: Id of volume_type.
+ provider: Class providing encryption support.
+ cipher: Encryption algorithm/mode to use.
+ key_size: Size of the encryption key, in bits.
+ control_location: Notional service where encryption is performed.
+ """
+ url = "/types/%s/encryption" % str(vol_type_id)
+ post_body = {}
+ post_body.update(kwargs)
+ post_body = json.dumps({'encryption': post_body})
+ resp, body = self.post(url, post_body)
+ body = json.loads(body)
+ return resp, body['encryption']
diff --git a/tempest/services/volume/json/extensions_client.py b/tempest/services/volume/json/extensions_client.py
index 257b7c8..9e182ea 100644
--- a/tempest/services/volume/json/extensions_client.py
+++ b/tempest/services/volume/json/extensions_client.py
@@ -15,13 +15,13 @@
import json
-from tempest.common.rest_client import RestClient
+from tempest.common import rest_client
from tempest import config
CONF = config.CONF
-class ExtensionsClientJSON(RestClient):
+class ExtensionsClientJSON(rest_client.RestClient):
def __init__(self, auth_provider):
super(ExtensionsClientJSON, self).__init__(auth_provider)
diff --git a/tempest/services/volume/json/snapshots_client.py b/tempest/services/volume/json/snapshots_client.py
index ba33c49..2dff63d 100644
--- a/tempest/services/volume/json/snapshots_client.py
+++ b/tempest/services/volume/json/snapshots_client.py
@@ -14,7 +14,7 @@
import time
import urllib
-from tempest.common.rest_client import RestClient
+from tempest.common import rest_client
from tempest import config
from tempest import exceptions
from tempest.openstack.common import log as logging
@@ -24,7 +24,7 @@
LOG = logging.getLogger(__name__)
-class SnapshotsClientJSON(RestClient):
+class SnapshotsClientJSON(rest_client.RestClient):
"""Client class to send CRUD Volume API requests."""
def __init__(self, auth_provider):
diff --git a/tempest/services/volume/json/volumes_client.py b/tempest/services/volume/json/volumes_client.py
index 2183c56..e4d2e8d 100644
--- a/tempest/services/volume/json/volumes_client.py
+++ b/tempest/services/volume/json/volumes_client.py
@@ -17,14 +17,14 @@
import time
import urllib
-from tempest.common.rest_client import RestClient
+from tempest.common import rest_client
from tempest import config
from tempest import exceptions
CONF = config.CONF
-class VolumesClientJSON(RestClient):
+class VolumesClientJSON(rest_client.RestClient):
"""
Client class to send CRUD Volume API requests to a Cinder endpoint
"""
diff --git a/tempest/services/volume/v2/json/volumes_client.py b/tempest/services/volume/v2/json/volumes_client.py
index bd98402..5bfa75f 100644
--- a/tempest/services/volume/v2/json/volumes_client.py
+++ b/tempest/services/volume/v2/json/volumes_client.py
@@ -17,14 +17,14 @@
import time
import urllib
-from tempest.common.rest_client import RestClient
+from tempest.common import rest_client
from tempest import config
from tempest import exceptions
CONF = config.CONF
-class VolumesV2ClientJSON(RestClient):
+class VolumesV2ClientJSON(rest_client.RestClient):
"""
Client class to send CRUD Volume V2 API requests to a Cinder endpoint
"""
diff --git a/tempest/services/volume/v2/xml/volumes_client.py b/tempest/services/volume/v2/xml/volumes_client.py
index bc57842..0b8f47c 100644
--- a/tempest/services/volume/v2/xml/volumes_client.py
+++ b/tempest/services/volume/v2/xml/volumes_client.py
@@ -21,11 +21,7 @@
from tempest.common import rest_client
from tempest import config
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
-from tempest.services.compute.xml.common import XMLNS_11
+from tempest.services.compute.xml import common
CONF = config.CONF
@@ -56,7 +52,7 @@
meta.text) for meta in
child.getchildren())
else:
- vol[tag] = xml_to_json(child)
+ vol[tag] = common.xml_to_json(child)
return vol
def get_attachment_from_volume(self, volume):
@@ -134,15 +130,15 @@
image
"""
# NOTE(afazekas): it should use a volume namespace
- volume = Element("volume", xmlns=XMLNS_11, size=size)
+ volume = common.Element("volume", xmlns=common.XMLNS_11, size=size)
if 'metadata' in kwargs:
- _metadata = Element('metadata')
+ _metadata = common.Element('metadata')
volume.append(_metadata)
for key, value in kwargs['metadata'].items():
- meta = Element('meta')
+ meta = common.Element('meta')
meta.add_attr('key', key)
- meta.append(Text(value))
+ meta.append(common.Text(value))
_metadata.append(meta)
attr_to_add = kwargs.copy()
del attr_to_add['metadata']
@@ -152,17 +148,17 @@
for key, value in attr_to_add.items():
volume.add_attr(key, value)
- resp, body = self.post('volumes', str(Document(volume)))
- body = xml_to_json(etree.fromstring(body))
+ resp, body = self.post('volumes', str(common.Document(volume)))
+ body = common.xml_to_json(etree.fromstring(body))
return resp, body
def update_volume(self, volume_id, **kwargs):
"""Updates the Specified Volume."""
- put_body = Element("volume", xmlns=XMLNS_11, **kwargs)
+ put_body = common.Element("volume", xmlns=common.XMLNS_11, **kwargs)
resp, body = self.put('volumes/%s' % volume_id,
- str(Document(put_body)))
- body = xml_to_json(etree.fromstring(body))
+ str(common.Document(put_body)))
+ body = common.xml_to_json(etree.fromstring(body))
return resp, body
def delete_volume(self, volume_id):
@@ -198,108 +194,107 @@
def attach_volume(self, volume_id, instance_uuid, mountpoint):
"""Attaches a volume to a given instance on a given mountpoint."""
- post_body = Element("os-attach",
- instance_uuid=instance_uuid,
- mountpoint=mountpoint
- )
+ post_body = common.Element("os-attach",
+ instance_uuid=instance_uuid,
+ mountpoint=mountpoint
+ )
url = 'volumes/%s/action' % str(volume_id)
- resp, body = self.post(url, str(Document(post_body)))
+ resp, body = self.post(url, str(common.Document(post_body)))
if body:
- body = xml_to_json(etree.fromstring(body))
+ body = common.xml_to_json(etree.fromstring(body))
return resp, body
def detach_volume(self, volume_id):
"""Detaches a volume from an instance."""
- post_body = Element("os-detach")
+ post_body = common.Element("os-detach")
url = 'volumes/%s/action' % str(volume_id)
- resp, body = self.post(url, str(Document(post_body)))
+ resp, body = self.post(url, str(common.Document(post_body)))
if body:
- body = xml_to_json(etree.fromstring(body))
+ body = common.xml_to_json(etree.fromstring(body))
return resp, body
def upload_volume(self, volume_id, image_name, disk_format):
"""Uploads a volume in Glance."""
- post_body = Element("os-volume_upload_image",
- image_name=image_name,
- disk_format=disk_format)
+ post_body = common.Element("os-volume_upload_image",
+ image_name=image_name,
+ disk_format=disk_format)
url = 'volumes/%s/action' % str(volume_id)
- resp, body = self.post(url, str(Document(post_body)))
- volume = xml_to_json(etree.fromstring(body))
+ resp, body = self.post(url, str(common.Document(post_body)))
+ volume = common.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)
+ post_body = common.Element("os-extend",
+ new_size=extend_size)
url = 'volumes/%s/action' % str(volume_id)
- resp, body = self.post(url, str(Document(post_body)))
+ resp, body = self.post(url, str(common.Document(post_body)))
if body:
- body = xml_to_json(etree.fromstring(body))
+ body = common.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
- )
+ post_body = common.Element("os-reset_status",
+ status=status
+ )
url = 'volumes/%s/action' % str(volume_id)
- resp, body = self.post(url, str(Document(post_body)))
+ resp, body = self.post(url, str(common.Document(post_body)))
if body:
- body = xml_to_json(etree.fromstring(body))
+ body = common.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")
+ post_body = common.Element("os-begin_detaching")
url = 'volumes/%s/action' % str(volume_id)
- resp, body = self.post(url, str(Document(post_body)))
+ resp, body = self.post(url, str(common.Document(post_body)))
if body:
- body = xml_to_json(etree.fromstring(body))
+ body = common.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")
+ post_body = common.Element("os-roll_detaching")
url = 'volumes/%s/action' % str(volume_id)
- resp, body = self.post(url, str(Document(post_body)))
+ resp, body = self.post(url, str(common.Document(post_body)))
if body:
- body = xml_to_json(etree.fromstring(body))
+ body = common.xml_to_json(etree.fromstring(body))
return resp, body
def reserve_volume(self, volume_id):
"""Reserves a volume."""
- post_body = Element("os-reserve")
+ post_body = common.Element("os-reserve")
url = 'volumes/%s/action' % str(volume_id)
- resp, body = self.post(url, str(Document(post_body)))
+ resp, body = self.post(url, str(common.Document(post_body)))
if body:
- body = xml_to_json(etree.fromstring(body))
+ body = common.xml_to_json(etree.fromstring(body))
return resp, body
def unreserve_volume(self, volume_id):
"""Restore a reserved volume ."""
- post_body = Element("os-unreserve")
+ post_body = common.Element("os-unreserve")
url = 'volumes/%s/action' % str(volume_id)
- resp, body = self.post(url, str(Document(post_body)))
+ resp, body = self.post(url, str(common.Document(post_body)))
if body:
- body = xml_to_json(etree.fromstring(body))
+ body = common.xml_to_json(etree.fromstring(body))
return resp, body
def create_volume_transfer(self, vol_id, name=None):
"""Create a volume transfer."""
- post_body = Element("transfer",
- volume_id=vol_id)
+ post_body = common.Element("transfer", volume_id=vol_id)
if name:
post_body.add_attr('name', name)
resp, body = self.post('os-volume-transfer',
- str(Document(post_body)))
- volume = xml_to_json(etree.fromstring(body))
+ str(common.Document(post_body)))
+ volume = common.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)
- volume = xml_to_json(etree.fromstring(body))
+ volume = common.xml_to_json(etree.fromstring(body))
return resp, volume
def list_volume_transfers(self, params=None):
@@ -321,7 +316,7 @@
tag = child.tag
if tag.startswith("{"):
tag = tag.split("}", 1)
- vol[tag] = xml_to_json(child)
+ vol[tag] = common.xml_to_json(child)
return vol
def delete_volume_transfer(self, transfer_id):
@@ -330,36 +325,36 @@
def accept_volume_transfer(self, transfer_id, transfer_auth_key):
"""Accept a volume transfer."""
- post_body = Element("accept", auth_key=transfer_auth_key)
+ post_body = common.Element("accept", auth_key=transfer_auth_key)
url = 'os-volume-transfer/%s/accept' % transfer_id
- resp, body = self.post(url, str(Document(post_body)))
- volume = xml_to_json(etree.fromstring(body))
+ resp, body = self.post(url, str(common.Document(post_body)))
+ volume = common.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)
+ post_body = common.Element("os-update_readonly_flag",
+ readonly=readonly)
url = 'volumes/%s/action' % str(volume_id)
- resp, body = self.post(url, str(Document(post_body)))
+ resp, body = self.post(url, str(common.Document(post_body)))
if body:
- body = xml_to_json(etree.fromstring(body))
+ body = common.xml_to_json(etree.fromstring(body))
return resp, body
def force_delete_volume(self, volume_id):
"""Force Delete Volume."""
- post_body = Element("os-force_delete")
+ post_body = common.Element("os-force_delete")
url = 'volumes/%s/action' % str(volume_id)
- resp, body = self.post(url, str(Document(post_body)))
+ resp, body = self.post(url, str(common.Document(post_body)))
if body:
- body = xml_to_json(etree.fromstring(body))
+ body = common.xml_to_json(etree.fromstring(body))
return resp, body
def _metadata_body(self, meta):
- post_body = Element('metadata')
+ post_body = common.Element('metadata')
for k, v in meta.items():
- data = Element('meta', key=k)
- data.append(Text(v))
+ data = common.Element('meta', key=k)
+ data.append(common.Text(v))
post_body.append(data)
return post_body
@@ -374,7 +369,7 @@
"""Create metadata for the volume."""
post_body = self._metadata_body(metadata)
resp, body = self.post('volumes/%s/metadata' % volume_id,
- str(Document(post_body)))
+ str(common.Document(post_body)))
body = self._parse_key_value(etree.fromstring(body))
return resp, body
@@ -389,18 +384,18 @@
"""Update metadata for the volume."""
put_body = self._metadata_body(metadata)
url = "volumes/%s/metadata" % str(volume_id)
- resp, body = self.put(url, str(Document(put_body)))
+ resp, body = self.put(url, str(common.Document(put_body)))
body = self._parse_key_value(etree.fromstring(body))
return resp, body
def update_volume_metadata_item(self, volume_id, id, meta_item):
"""Update metadata item for the volume."""
for k, v in meta_item.items():
- put_body = Element('meta', key=k)
- put_body.append(Text(v))
+ put_body = common.Element('meta', key=k)
+ put_body.append(common.Text(v))
url = "volumes/%s/metadata/%s" % (str(volume_id), str(id))
- resp, body = self.put(url, str(Document(put_body)))
- body = xml_to_json(etree.fromstring(body))
+ resp, body = self.put(url, str(common.Document(put_body)))
+ body = common.xml_to_json(etree.fromstring(body))
return resp, body
def delete_volume_metadata_item(self, volume_id, id):
diff --git a/tempest/services/volume/xml/admin/volume_hosts_client.py b/tempest/services/volume/xml/admin/volume_hosts_client.py
index fb84c83..e34b9f0 100644
--- a/tempest/services/volume/xml/admin/volume_hosts_client.py
+++ b/tempest/services/volume/xml/admin/volume_hosts_client.py
@@ -19,7 +19,7 @@
from tempest.common import rest_client
from tempest import config
-from tempest.services.compute.xml.common import xml_to_json
+from tempest.services.compute.xml import common
CONF = config.CONF
@@ -58,7 +58,7 @@
for child in node.getchildren():
tag_list = child.tag.split('}', 1)
if tag_list[0] == "host":
- array.append(xml_to_json(child))
+ array.append(common.xml_to_json(child))
return array
def list_hosts(self, params=None):
diff --git a/tempest/services/volume/xml/admin/volume_quotas_client.py b/tempest/services/volume/xml/admin/volume_quotas_client.py
new file mode 100644
index 0000000..d2eac34
--- /dev/null
+++ b/tempest/services/volume/xml/admin/volume_quotas_client.py
@@ -0,0 +1,70 @@
+# Copyright (C) 2014 eNovance SAS <licensing@enovance.com>
+#
+# Author: Sylvain Baubeau <sylvain.baubeau@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 ast import literal_eval
+from lxml import etree
+
+from tempest import config
+from tempest.services.compute.xml import common as xml
+from tempest.services.volume.json.admin import volume_quotas_client
+
+CONF = config.CONF
+
+
+class VolumeQuotasClientXML(volume_quotas_client.VolumeQuotasClientJSON):
+ """
+ Client class to send CRUD Volume Quotas API requests to a Cinder endpoint
+ """
+
+ TYPE = "xml"
+
+ def _format_quota(self, q):
+ quota = {}
+ for k, v in q.items():
+ try:
+ v = literal_eval(v)
+ except (ValueError, SyntaxError):
+ pass
+
+ quota[k] = v
+
+ return quota
+
+ def get_quota_usage(self, tenant_id):
+ """List the quota set for a tenant."""
+
+ resp, body = self.get_quota_set(tenant_id, params={'usage': True})
+ return resp, self._format_quota(body)
+
+ def update_quota_set(self, tenant_id, gigabytes=None, volumes=None,
+ snapshots=None):
+ post_body = {}
+ element = xml.Element("quota_set")
+
+ if gigabytes is not None:
+ post_body['gigabytes'] = gigabytes
+
+ if volumes is not None:
+ post_body['volumes'] = volumes
+
+ if snapshots is not None:
+ post_body['snapshots'] = snapshots
+
+ xml.deep_dict_to_xml(element, post_body)
+ resp, body = self.put('os-quota-sets/%s' % tenant_id,
+ str(xml.Document(element)))
+ body = xml.xml_to_json(etree.fromstring(body))
+ return resp, self._format_quota(body)
diff --git a/tempest/services/volume/xml/admin/volume_types_client.py b/tempest/services/volume/xml/admin/volume_types_client.py
index 77bafec..1fa3e73 100644
--- a/tempest/services/volume/xml/admin/volume_types_client.py
+++ b/tempest/services/volume/xml/admin/volume_types_client.py
@@ -20,11 +20,7 @@
from tempest.common import rest_client
from tempest import config
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
-from tempest.services.compute.xml.common import XMLNS_11
+from tempest.services.compute.xml import common
CONF = config.CONF
@@ -53,7 +49,7 @@
meta.text)
for meta in list(child))
else:
- vol_type[tag] = xml_to_json(child)
+ vol_type[tag] = common.xml_to_json(child)
return vol_type
def _parse_volume_type_extra_specs(self, body):
@@ -64,7 +60,7 @@
if tag.startswith("{"):
ns, tag = tag.split("}", 1)
else:
- extra_spec[tag] = xml_to_json(child)
+ extra_spec[tag] = common.xml_to_json(child)
return extra_spec
def list_volume_types(self, params=None):
@@ -95,22 +91,22 @@
Following optional keyword arguments are accepted:
extra_specs: A dictionary of values to be used as extra_specs.
"""
- vol_type = Element("volume_type", xmlns=XMLNS_11)
+ vol_type = common.Element("volume_type", xmlns=common.XMLNS_11)
if name:
vol_type.add_attr('name', name)
extra_specs = kwargs.get('extra_specs')
if extra_specs:
- _extra_specs = Element('extra_specs')
+ _extra_specs = common.Element('extra_specs')
vol_type.append(_extra_specs)
for key, value in extra_specs.items():
- spec = Element('extra_spec')
+ spec = common.Element('extra_spec')
spec.add_attr('key', key)
- spec.append(Text(value))
+ spec.append(common.Text(value))
_extra_specs.append(spec)
- resp, body = self.post('types', str(Document(vol_type)))
- body = xml_to_json(etree.fromstring(body))
+ resp, body = self.post('types', str(common.Document(vol_type)))
+ body = common.xml_to_json(etree.fromstring(body))
return resp, body
def delete_volume_type(self, type_id):
@@ -147,21 +143,21 @@
extra_specs: A dictionary of values to be used as extra_specs.
"""
url = "types/%s/extra_specs" % str(vol_type_id)
- extra_specs = Element("extra_specs", xmlns=XMLNS_11)
+ extra_specs = common.Element("extra_specs", xmlns=common.XMLNS_11)
if extra_spec:
if isinstance(extra_spec, list):
extra_specs.append(extra_spec)
else:
for key, value in extra_spec.items():
- spec = Element('extra_spec')
+ spec = common.Element('extra_spec')
spec.add_attr('key', key)
- spec.append(Text(value))
+ spec.append(common.Text(value))
extra_specs.append(spec)
else:
extra_specs = None
- resp, body = self.post(url, str(Document(extra_specs)))
- body = xml_to_json(etree.fromstring(body))
+ resp, body = self.post(url, str(common.Document(extra_specs)))
+ body = common.xml_to_json(etree.fromstring(body))
return resp, body
def delete_volume_type_extra_specs(self, vol_id, extra_spec_name):
@@ -180,17 +176,17 @@
"""
url = "types/%s/extra_specs/%s" % (str(vol_type_id),
str(extra_spec_name))
- extra_specs = Element("extra_specs", xmlns=XMLNS_11)
+ extra_specs = common.Element("extra_specs", xmlns=common.XMLNS_11)
if extra_spec is not None:
for key, value in extra_spec.items():
- spec = Element('extra_spec')
+ spec = common.Element('extra_spec')
spec.add_attr('key', key)
- spec.append(Text(value))
+ spec.append(common.Text(value))
extra_specs.append(spec)
- resp, body = self.put(url, str(Document(extra_specs)))
- body = xml_to_json(etree.fromstring(body))
+ resp, body = self.put(url, str(common.Document(extra_specs)))
+ body = common.xml_to_json(etree.fromstring(body))
return resp, body
def is_resource_deleted(self, id):
diff --git a/tempest/services/volume/xml/extensions_client.py b/tempest/services/volume/xml/extensions_client.py
index 1ea974f..4861733 100644
--- a/tempest/services/volume/xml/extensions_client.py
+++ b/tempest/services/volume/xml/extensions_client.py
@@ -17,7 +17,7 @@
from tempest.common import rest_client
from tempest import config
-from tempest.services.compute.xml.common import xml_to_json
+from tempest.services.compute.xml import common
CONF = config.CONF
@@ -32,7 +32,7 @@
def _parse_array(self, node):
array = []
for child in node:
- array.append(xml_to_json(child))
+ array.append(common.xml_to_json(child))
return array
def list_extensions(self):
diff --git a/tempest/services/volume/xml/snapshots_client.py b/tempest/services/volume/xml/snapshots_client.py
index 458001b..9ad86d2 100644
--- a/tempest/services/volume/xml/snapshots_client.py
+++ b/tempest/services/volume/xml/snapshots_client.py
@@ -19,11 +19,7 @@
from tempest import config
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_11
+from tempest.services.compute.xml import common
CONF = config.CONF
@@ -52,7 +48,7 @@
body = etree.fromstring(body)
snapshots = []
for snap in body:
- snapshots.append(xml_to_json(snap))
+ snapshots.append(common.xml_to_json(snap))
return resp, snapshots
def list_snapshots_with_detail(self, params=None):
@@ -66,7 +62,7 @@
body = etree.fromstring(body)
snapshots = []
for snap in body:
- snapshots.append(xml_to_json(snap))
+ snapshots.append(common.xml_to_json(snap))
return resp, snapshots
def get_snapshot(self, snapshot_id):
@@ -74,7 +70,7 @@
url = "snapshots/%s" % str(snapshot_id)
resp, body = self.get(url)
body = etree.fromstring(body)
- return resp, xml_to_json(body)
+ return resp, common.xml_to_json(body)
def create_snapshot(self, volume_id, **kwargs):
"""Creates a new snapshot.
@@ -84,20 +80,22 @@
display_description: User friendly snapshot description.
"""
# NOTE(afazekas): it should use the volume namespace
- snapshot = Element("snapshot", xmlns=XMLNS_11, volume_id=volume_id)
+ snapshot = common.Element("snapshot", xmlns=common.XMLNS_11,
+ volume_id=volume_id)
for key, value in kwargs.items():
snapshot.add_attr(key, value)
- resp, body = self.post('snapshots', str(Document(snapshot)))
- body = xml_to_json(etree.fromstring(body))
+ resp, body = self.post('snapshots',
+ str(common.Document(snapshot)))
+ body = common.xml_to_json(etree.fromstring(body))
return resp, body
def update_snapshot(self, snapshot_id, **kwargs):
"""Updates a snapshot."""
- put_body = Element("snapshot", xmlns=XMLNS_11, **kwargs)
+ put_body = common.Element("snapshot", xmlns=common.XMLNS_11, **kwargs)
resp, body = self.put('snapshots/%s' % snapshot_id,
- str(Document(put_body)))
- body = xml_to_json(etree.fromstring(body))
+ str(common.Document(put_body)))
+ body = common.xml_to_json(etree.fromstring(body))
return resp, body
# NOTE(afazekas): just for the wait function
@@ -150,32 +148,30 @@
def reset_snapshot_status(self, snapshot_id, status):
"""Reset the specified snapshot's status."""
- post_body = Element("os-reset_status",
- status=status
- )
+ post_body = common.Element("os-reset_status", status=status)
url = 'snapshots/%s/action' % str(snapshot_id)
- resp, body = self.post(url, str(Document(post_body)))
+ resp, body = self.post(url, str(common.Document(post_body)))
if body:
- body = xml_to_json(etree.fromstring(body))
+ body = common.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
- )
+ post_body = common.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)))
+ resp, body = self.post(url, str(common.Document(post_body)))
if body:
- body = xml_to_json(etree.fromstring(body))
+ body = common.xml_to_json(etree.fromstring(body))
return resp, body
def _metadata_body(self, meta):
- post_body = Element('metadata')
+ post_body = common.Element('metadata')
for k, v in meta.items():
- data = Element('meta', key=k)
- data.append(Text(v))
+ data = common.Element('meta', key=k)
+ data.append(common.Text(v))
post_body.append(data)
return post_body
@@ -190,7 +186,7 @@
"""Create metadata for the snapshot."""
post_body = self._metadata_body(metadata)
resp, body = self.post('snapshots/%s/metadata' % snapshot_id,
- str(Document(post_body)))
+ str(common.Document(post_body)))
body = self._parse_key_value(etree.fromstring(body))
return resp, body
@@ -205,18 +201,18 @@
"""Update metadata for the snapshot."""
put_body = self._metadata_body(metadata)
url = "snapshots/%s/metadata" % str(snapshot_id)
- resp, body = self.put(url, str(Document(put_body)))
+ resp, body = self.put(url, str(common.Document(put_body)))
body = self._parse_key_value(etree.fromstring(body))
return resp, body
def update_snapshot_metadata_item(self, snapshot_id, id, meta_item):
"""Update metadata item for the snapshot."""
for k, v in meta_item.items():
- put_body = Element('meta', key=k)
- put_body.append(Text(v))
+ put_body = common.Element('meta', key=k)
+ put_body.append(common.Text(v))
url = "snapshots/%s/metadata/%s" % (str(snapshot_id), str(id))
- resp, body = self.put(url, str(Document(put_body)))
- body = xml_to_json(etree.fromstring(body))
+ resp, body = self.put(url, str(common.Document(put_body)))
+ body = common.xml_to_json(etree.fromstring(body))
return resp, body
def delete_snapshot_metadata_item(self, snapshot_id, id):
@@ -226,9 +222,9 @@
def force_delete_snapshot(self, snapshot_id):
"""Force Delete Snapshot."""
- post_body = Element("os-force_delete")
+ post_body = common.Element("os-force_delete")
url = 'snapshots/%s/action' % str(snapshot_id)
- resp, body = self.post(url, str(Document(post_body)))
+ resp, body = self.post(url, str(common.Document(post_body)))
if body:
- body = xml_to_json(etree.fromstring(body))
+ body = common.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 aef1e3c..8e886ce 100644
--- a/tempest/services/volume/xml/volumes_client.py
+++ b/tempest/services/volume/xml/volumes_client.py
@@ -17,16 +17,12 @@
import urllib
from lxml import etree
-from xml.sax.saxutils import escape
+from xml.sax import saxutils
from tempest.common import rest_client
from tempest import config
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
-from tempest.services.compute.xml.common import XMLNS_11
+from tempest.services.compute.xml import common
CONF = config.CONF
@@ -55,7 +51,7 @@
meta.text) for meta in
child.getchildren())
else:
- vol[tag] = xml_to_json(child)
+ vol[tag] = common.xml_to_json(child)
return vol
def get_attachment_from_volume(self, volume):
@@ -135,15 +131,15 @@
image
"""
# NOTE(afazekas): it should use a volume namespace
- volume = Element("volume", xmlns=XMLNS_11, size=size)
+ volume = common.Element("volume", xmlns=common.XMLNS_11, size=size)
if 'metadata' in kwargs:
- _metadata = Element('metadata')
+ _metadata = common.Element('metadata')
volume.append(_metadata)
for key, value in kwargs['metadata'].items():
- meta = Element('meta')
+ meta = common.Element('meta')
meta.add_attr('key', key)
- meta.append(Text(value))
+ meta.append(common.Text(value))
_metadata.append(meta)
attr_to_add = kwargs.copy()
del attr_to_add['metadata']
@@ -153,17 +149,17 @@
for key, value in attr_to_add.items():
volume.add_attr(key, value)
- resp, body = self.post('volumes', str(Document(volume)))
- body = xml_to_json(etree.fromstring(body))
+ resp, body = self.post('volumes', str(common.Document(volume)))
+ body = common.xml_to_json(etree.fromstring(body))
return resp, body
def update_volume(self, volume_id, **kwargs):
"""Updates the Specified Volume."""
- put_body = Element("volume", xmlns=XMLNS_11, **kwargs)
+ put_body = common.Element("volume", xmlns=common.XMLNS_11, **kwargs)
resp, body = self.put('volumes/%s' % volume_id,
- str(Document(put_body)))
- body = xml_to_json(etree.fromstring(body))
+ str(common.Document(put_body)))
+ body = common.xml_to_json(etree.fromstring(body))
return resp, body
def delete_volume(self, volume_id):
@@ -199,108 +195,108 @@
def attach_volume(self, volume_id, instance_uuid, mountpoint):
"""Attaches a volume to a given instance on a given mountpoint."""
- post_body = Element("os-attach",
- instance_uuid=instance_uuid,
- mountpoint=mountpoint
- )
+ post_body = common.Element("os-attach",
+ instance_uuid=instance_uuid,
+ mountpoint=mountpoint
+ )
url = 'volumes/%s/action' % str(volume_id)
- resp, body = self.post(url, str(Document(post_body)))
+ resp, body = self.post(url, str(common.Document(post_body)))
if body:
- body = xml_to_json(etree.fromstring(body))
+ body = common.xml_to_json(etree.fromstring(body))
return resp, body
def detach_volume(self, volume_id):
"""Detaches a volume from an instance."""
- post_body = Element("os-detach")
+ post_body = common.Element("os-detach")
url = 'volumes/%s/action' % str(volume_id)
- resp, body = self.post(url, str(Document(post_body)))
+ resp, body = self.post(url, str(common.Document(post_body)))
if body:
- body = xml_to_json(etree.fromstring(body))
+ body = common.xml_to_json(etree.fromstring(body))
return resp, body
def upload_volume(self, volume_id, image_name, disk_format):
"""Uploads a volume in Glance."""
- post_body = Element("os-volume_upload_image",
- image_name=image_name,
- disk_format=disk_format)
+ post_body = common.Element("os-volume_upload_image",
+ image_name=image_name,
+ disk_format=disk_format)
url = 'volumes/%s/action' % str(volume_id)
- resp, body = self.post(url, str(Document(post_body)))
- volume = xml_to_json(etree.fromstring(body))
+ resp, body = self.post(url, str(common.Document(post_body)))
+ volume = common.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)
+ post_body = common.Element("os-extend",
+ new_size=extend_size)
url = 'volumes/%s/action' % str(volume_id)
- resp, body = self.post(url, str(Document(post_body)))
+ resp, body = self.post(url, str(common.Document(post_body)))
if body:
- body = xml_to_json(etree.fromstring(body))
+ body = common.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
- )
+ post_body = common.Element("os-reset_status",
+ status=status
+ )
url = 'volumes/%s/action' % str(volume_id)
- resp, body = self.post(url, str(Document(post_body)))
+ resp, body = self.post(url, str(common.Document(post_body)))
if body:
- body = xml_to_json(etree.fromstring(body))
+ body = common.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")
+ post_body = common.Element("os-begin_detaching")
url = 'volumes/%s/action' % str(volume_id)
- resp, body = self.post(url, str(Document(post_body)))
+ resp, body = self.post(url, str(common.Document(post_body)))
if body:
- body = xml_to_json(etree.fromstring(body))
+ body = common.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")
+ post_body = common.Element("os-roll_detaching")
url = 'volumes/%s/action' % str(volume_id)
- resp, body = self.post(url, str(Document(post_body)))
+ resp, body = self.post(url, str(common.Document(post_body)))
if body:
- body = xml_to_json(etree.fromstring(body))
+ body = common.xml_to_json(etree.fromstring(body))
return resp, body
def reserve_volume(self, volume_id):
"""Reserves a volume."""
- post_body = Element("os-reserve")
+ post_body = common.Element("os-reserve")
url = 'volumes/%s/action' % str(volume_id)
- resp, body = self.post(url, str(Document(post_body)))
+ resp, body = self.post(url, str(common.Document(post_body)))
if body:
- body = xml_to_json(etree.fromstring(body))
+ body = common.xml_to_json(etree.fromstring(body))
return resp, body
def unreserve_volume(self, volume_id):
"""Restore a reserved volume ."""
- post_body = Element("os-unreserve")
+ post_body = common.Element("os-unreserve")
url = 'volumes/%s/action' % str(volume_id)
- resp, body = self.post(url, str(Document(post_body)))
+ resp, body = self.post(url, str(common.Document(post_body)))
if body:
- body = xml_to_json(etree.fromstring(body))
+ body = common.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)
+ post_body = common.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)))
- volume = xml_to_json(etree.fromstring(body))
+ str(common.Document(post_body)))
+ volume = common.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)
- volume = xml_to_json(etree.fromstring(body))
+ volume = common.xml_to_json(etree.fromstring(body))
return resp, volume
def list_volume_transfers(self, params=None):
@@ -322,7 +318,7 @@
tag = child.tag
if tag.startswith("{"):
tag = tag.split("}", 1)
- vol[tag] = xml_to_json(child)
+ vol[tag] = common.xml_to_json(child)
return vol
def delete_volume_transfer(self, transfer_id):
@@ -331,37 +327,37 @@
def accept_volume_transfer(self, transfer_id, transfer_auth_key):
"""Accept a volume transfer."""
- post_body = Element("accept", auth_key=transfer_auth_key)
+ post_body = common.Element("accept", auth_key=transfer_auth_key)
url = 'os-volume-transfer/%s/accept' % transfer_id
- resp, body = self.post(url, str(Document(post_body)))
- volume = xml_to_json(etree.fromstring(body))
+ resp, body = self.post(url, str(common.Document(post_body)))
+ volume = common.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)
+ post_body = common.Element("os-update_readonly_flag",
+ readonly=readonly)
url = 'volumes/%s/action' % str(volume_id)
- resp, body = self.post(url, str(Document(post_body)))
+ resp, body = self.post(url, str(common.Document(post_body)))
if body:
- body = xml_to_json(etree.fromstring(body))
+ body = common.xml_to_json(etree.fromstring(body))
return resp, body
def force_delete_volume(self, volume_id):
"""Force Delete Volume."""
- post_body = Element("os-force_delete")
+ post_body = common.Element("os-force_delete")
url = 'volumes/%s/action' % str(volume_id)
- resp, body = self.post(url, str(Document(post_body)))
+ resp, body = self.post(url, str(common.Document(post_body)))
if body:
- body = xml_to_json(etree.fromstring(body))
+ body = common.xml_to_json(etree.fromstring(body))
return resp, body
def _metadata_body(self, meta):
- post_body = Element('metadata')
+ post_body = common.Element('metadata')
for k, v in meta.items():
- data = Element('meta', key=k)
+ data = common.Element('meta', key=k)
# Escape value to allow for special XML chars
- data.append(Text(escape(v)))
+ data.append(common.Text(saxutils.escape(v)))
post_body.append(data)
return post_body
@@ -376,7 +372,7 @@
"""Create metadata for the volume."""
post_body = self._metadata_body(metadata)
resp, body = self.post('volumes/%s/metadata' % volume_id,
- str(Document(post_body)))
+ str(common.Document(post_body)))
body = self._parse_key_value(etree.fromstring(body))
return resp, body
@@ -391,18 +387,18 @@
"""Update metadata for the volume."""
put_body = self._metadata_body(metadata)
url = "volumes/%s/metadata" % str(volume_id)
- resp, body = self.put(url, str(Document(put_body)))
+ resp, body = self.put(url, str(common.Document(put_body)))
body = self._parse_key_value(etree.fromstring(body))
return resp, body
def update_volume_metadata_item(self, volume_id, id, meta_item):
"""Update metadata item for the volume."""
for k, v in meta_item.items():
- put_body = Element('meta', key=k)
- put_body.append(Text(v))
+ put_body = common.Element('meta', key=k)
+ put_body.append(common.Text(v))
url = "volumes/%s/metadata/%s" % (str(volume_id), str(id))
- resp, body = self.put(url, str(Document(put_body)))
- body = xml_to_json(etree.fromstring(body))
+ resp, body = self.put(url, str(common.Document(put_body)))
+ body = common.xml_to_json(etree.fromstring(body))
return resp, body
def delete_volume_metadata_item(self, volume_id, id):
diff --git a/tempest/stress/actions/ssh_floating.py b/tempest/stress/actions/ssh_floating.py
index a34a20d..c330165 100644
--- a/tempest/stress/actions/ssh_floating.py
+++ b/tempest/stress/actions/ssh_floating.py
@@ -69,7 +69,7 @@
servers_client = self.manager.servers_client
self.logger.info("creating %s" % name)
vm_args = self.vm_extra_args.copy()
- vm_args['security_groups'] = [{'name': self.sec_grp}]
+ vm_args['security_groups'] = [self.sec_grp]
resp, server = servers_client.create_server(name, self.image,
self.flavor,
**vm_args)
@@ -90,16 +90,15 @@
sec_grp_cli = self.manager.security_groups_client
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']
+ _, self.sec_grp = sec_grp_cli.create_security_group(s_name,
+ s_description)
create_rule = sec_grp_cli.create_security_group_rule
- create_rule(self.sec_grp, 'tcp', 22, 22)
- create_rule(self.sec_grp, 'icmp', -1, -1)
+ create_rule(self.sec_grp['id'], 'tcp', 22, 22)
+ create_rule(self.sec_grp['id'], 'icmp', -1, -1)
def _destroy_sec_grp(self):
sec_grp_cli = self.manager.security_groups_client
- sec_grp_cli.delete_security_group(self.sec_grp)
+ sec_grp_cli.delete_security_group(self.sec_grp['id'])
def _create_floating_ip(self):
floating_cli = self.manager.floating_ips_client
diff --git a/tempest/stress/cleanup.py b/tempest/stress/cleanup.py
index b46de35..2587331 100644
--- a/tempest/stress/cleanup.py
+++ b/tempest/stress/cleanup.py
@@ -45,6 +45,16 @@
except Exception:
pass
+ secgrp_client = admin_manager.security_groups_client
+ _, secgrp = secgrp_client.list_security_groups({"all_tenants": True})
+ secgrp_del = [grp for grp in secgrp if grp['name'] != 'default']
+ LOG.info("Cleanup::remove %s Security Group" % len(secgrp_del))
+ for g in secgrp_del:
+ try:
+ secgrp_client.delete_security_group(g['id'])
+ except Exception:
+ pass
+
_, floating_ips = admin_manager.floating_ips_client.list_floating_ips()
LOG.info("Cleanup::remove %s floating ips" % len(floating_ips))
for f in floating_ips:
diff --git a/tempest/test.py b/tempest/test.py
index 2125047..d2568a6 100644
--- a/tempest/test.py
+++ b/tempest/test.py
@@ -26,6 +26,8 @@
import testresources
import testtools
+from oslo.config import cfg
+
from tempest import clients
import tempest.common.generator.valid_generator as valid
from tempest.common import isolated_creds
@@ -64,6 +66,25 @@
return decorator
+def safe_setup(f):
+ """A decorator used to wrap the setUpClass for cleaning up resources
+ when setUpClass failed.
+ """
+
+ def decorator(cls):
+ try:
+ f(cls)
+ except Exception as se:
+ LOG.exception("setUpClass failed: %s" % se)
+ try:
+ cls.tearDownClass()
+ except Exception as te:
+ LOG.exception("tearDownClass failed: %s" % te)
+ raise se
+
+ return decorator
+
+
def services(*args, **kwargs):
"""A decorator used to set an attr for each service used in a test case
@@ -199,13 +220,11 @@
def validate_tearDownClass():
if at_exit_set:
- raise RuntimeError("tearDownClass does not call the super's "
- "tearDownClass in these classes: "
- + 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!")
+ LOG.error(
+ "tearDownClass does not call the super's "
+ "tearDownClass in these classes: \n"
+ + str(at_exit_set))
+
atexit.register(validate_tearDownClass)
@@ -353,6 +372,12 @@
'subnet': subnet,
'dhcp': dhcp}
+ def assertEmpty(self, list, msg=None):
+ self.assertTrue(len(list) == 0, msg)
+
+ def assertNotEmpty(self, list, msg=None):
+ self.assertTrue(len(list) > 0, msg)
+
class NegativeAutoTest(BaseTestCase):
@@ -363,6 +388,9 @@
super(NegativeAutoTest, cls).setUpClass()
os = cls.get_client_manager()
cls.client = os.negative_client
+ os_admin = clients.AdminManager(interface=cls._interface,
+ service=cls._service)
+ cls.admin_client = os_admin.negative_client
@staticmethod
def load_schema(file):
@@ -401,7 +429,17 @@
"""
description = NegativeAutoTest.load_schema(description_file)
LOG.debug(description)
- generator = importutils.import_class(CONF.negative.test_generator)()
+
+ # NOTE(mkoderer): since this will be executed on import level the
+ # config doesn't have to be in place (e.g. for the pep8 job).
+ # In this case simply return.
+ try:
+ generator = importutils.import_class(
+ CONF.negative.test_generator)()
+ except cfg.ConfigFilesNotFoundError:
+ LOG.critical(
+ "Tempest config not found. Test scenarios aren't created")
+ return
generator.validate_schema(description)
schema = description.get("json-schema", None)
resources = description.get("resources", [])
@@ -418,10 +456,13 @@
"expected_result": expected_result
}))
if schema is not None:
- for invalid in generator.generate(schema):
- scenario_list.append((invalid[0],
- {"schema": invalid[1],
- "expected_result": invalid[2]}))
+ for name, schema, expected_result in generator.generate(schema):
+ if (expected_result is None and
+ "default_result_code" in description):
+ expected_result = description["default_result_code"]
+ scenario_list.append((name,
+ {"schema": schema,
+ "expected_result": expected_result}))
LOG.debug(scenario_list)
return scenario_list
@@ -469,9 +510,17 @@
new_url, body = self._http_arguments(valid_schema, url, method)
elif hasattr(self, "schema"):
new_url, body = self._http_arguments(self.schema, url, method)
+ else:
+ raise Exception("testscenarios are not active. Please make sure "
+ "that your test runner supports the load_tests "
+ "mechanism")
- resp, resp_body = self.client.send_request(method, new_url,
- resources, body=body)
+ if "admin_client" in description and description["admin_client"]:
+ client = self.admin_client
+ else:
+ client = self.client
+ resp, resp_body = client.send_request(method, new_url,
+ resources, body=body)
self._check_negative_response(resp.status, resp_body)
def _http_arguments(self, json_dict, url, method):
diff --git a/tempest/tests/common/__init__.py b/tempest/tests/common/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/tests/common/__init__.py
diff --git a/tempest/tests/common/utils/__init__.py b/tempest/tests/common/utils/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/tests/common/utils/__init__.py
diff --git a/tempest/tests/common/utils/test_data_utils.py b/tempest/tests/common/utils/test_data_utils.py
new file mode 100644
index 0000000..7aafdb2
--- /dev/null
+++ b/tempest/tests/common/utils/test_data_utils.py
@@ -0,0 +1,77 @@
+# Copyright 2014 NEC Corporation.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+
+from tempest.common.utils import data_utils
+from tempest.tests import base
+
+
+class TestDataUtils(base.TestCase):
+
+ def test_rand_uuid(self):
+ actual = data_utils.rand_uuid()
+ self.assertIsInstance(actual, str)
+ self.assertRegexpMatches(actual, "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]"
+ "{4}-[0-9a-f]{4}-[0-9a-f]{12}$")
+ actual2 = data_utils.rand_uuid()
+ self.assertNotEqual(actual, actual2)
+
+ def test_rand_uuid_hex(self):
+ actual = data_utils.rand_uuid_hex()
+ self.assertIsInstance(actual, str)
+ self.assertRegexpMatches(actual, "^[0-9a-f]{32}$")
+
+ actual2 = data_utils.rand_uuid_hex()
+ self.assertNotEqual(actual, actual2)
+
+ def test_rand_name(self):
+ actual = data_utils.rand_name()
+ self.assertIsInstance(actual, str)
+ actual2 = data_utils.rand_name()
+ self.assertNotEqual(actual, actual2)
+
+ actual = data_utils.rand_name('foo')
+ self.assertTrue(actual.startswith('foo'))
+ actual2 = data_utils.rand_name('foo')
+ self.assertTrue(actual.startswith('foo'))
+ self.assertNotEqual(actual, actual2)
+
+ def test_rand_int(self):
+ actual = data_utils.rand_int_id()
+ self.assertIsInstance(actual, int)
+
+ actual2 = data_utils.rand_int_id()
+ self.assertNotEqual(actual, actual2)
+
+ def test_rand_mac_address(self):
+ actual = data_utils.rand_mac_address()
+ self.assertIsInstance(actual, str)
+ self.assertRegexpMatches(actual, "^([0-9a-f][0-9a-f]:){5}"
+ "[0-9a-f][0-9a-f]$")
+
+ actual2 = data_utils.rand_mac_address()
+ self.assertNotEqual(actual, actual2)
+
+ def test_parse_image_id(self):
+ actual = data_utils.parse_image_id("/foo/bar/deadbeaf")
+ self.assertEqual("deadbeaf", actual)
+
+ def test_arbitrary_string(self):
+ actual = data_utils.arbitrary_string()
+ self.assertEqual(actual, "test")
+ actual = data_utils.arbitrary_string(size=30, base_text="abc")
+ self.assertEqual(actual, "abc" * (30 / len("abc")))
+ actual = data_utils.arbitrary_string(size=5, base_text="deadbeaf")
+ self.assertEqual(actual, "deadb")
diff --git a/tempest/tests/common/utils/test_file_utils.py b/tempest/tests/common/utils/test_file_utils.py
new file mode 100644
index 0000000..99ae033
--- /dev/null
+++ b/tempest/tests/common/utils/test_file_utils.py
@@ -0,0 +1,32 @@
+# Copyright 2014 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 mock
+from mock import patch
+
+from tempest.common.utils import file_utils
+from tempest.tests import base
+
+
+class TestFileUtils(base.TestCase):
+
+ def test_have_effective_read_path(self):
+ with patch('__builtin__.open', mock.mock_open(), create=True):
+ result = file_utils.have_effective_read_access('fake_path')
+ self.assertTrue(result)
+
+ def test_not_effective_read_path(self):
+ result = file_utils.have_effective_read_access('fake_path')
+ self.assertFalse(result)
diff --git a/tempest/tests/common/utils/test_misc.py b/tempest/tests/common/utils/test_misc.py
new file mode 100644
index 0000000..b8c6184
--- /dev/null
+++ b/tempest/tests/common/utils/test_misc.py
@@ -0,0 +1,52 @@
+# Copyright 2014 NEC Corporation.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+
+from tempest.common.utils import misc
+from tempest.tests import base
+
+
+@misc.singleton
+class TestFoo(object):
+
+ count = 0
+
+ def increment(self):
+ self.count += 1
+ return self.count
+
+
+@misc.singleton
+class TestBar(object):
+
+ count = 0
+
+ def increment(self):
+ self.count += 1
+ return self.count
+
+
+class TestMisc(base.TestCase):
+
+ def test_singleton(self):
+ test = TestFoo()
+ self.assertEqual(0, test.count)
+ self.assertEqual(1, test.increment())
+ test2 = TestFoo()
+ self.assertEqual(1, test.count)
+ self.assertEqual(1, test2.count)
+ self.assertEqual(test, test2)
+ test3 = TestBar()
+ self.assertNotEqual(test, test3)
diff --git a/tempest/tests/fake_config.py b/tempest/tests/fake_config.py
index e941606..8a8ebb0 100644
--- a/tempest/tests/fake_config.py
+++ b/tempest/tests/fake_config.py
@@ -12,49 +12,33 @@
# License for the specific language governing permissions and limitations
# under the License.
+from oslo.config import cfg
-class FakeConfig(object):
+from tempest import config
+from tempest.openstack.common.fixture import config as conf_fixture
- class fake_compute(object):
- build_interval = 10
- build_timeout = 10
- class fake_identity(object):
- disable_ssl_certificate_validation = True
- catalog_type = 'identity'
- uri = 'http://fake_uri.com/auth'
- uri_v3 = 'http://fake_uri_v3.com/auth'
+class ConfigFixture(conf_fixture.Config):
- class fake_default_feature_enabled(object):
- api_extensions = ['all']
+ def __init__(self):
+ config.register_opts()
+ super(ConfigFixture, self).__init__()
- class fake_compute_feature_enabled(fake_default_feature_enabled):
- api_v3_extensions = ['all']
+ def setUp(self):
+ super(ConfigFixture, self).setUp()
+ self.conf.set_default('build_interval', 10, group='compute')
+ self.conf.set_default('build_timeout', 10, group='compute')
+ self.conf.set_default('disable_ssl_certificate_validation', True,
+ group='identity')
+ self.conf.set_default('uri', 'http://fake_uri.com/auth',
+ group='identity')
+ self.conf.set_default('uri_v3', 'http://fake_uri_v3.com/auth',
+ group='identity')
+ self.conf.set_default('neutron', True, group='service_available')
+ self.conf.set_default('heat', True, group='service_available')
- class fake_object_storage_discoverable_apis(object):
- discoverable_apis = ['all']
- class fake_service_available(object):
- nova = True
- glance = True
- cinder = True
- heat = True
- neutron = True
- swift = True
- horizon = True
-
- class fake_negative(object):
- test_generator = 'tempest.common.' \
- 'generator.negative_generator.NegativeTestGenerator'
-
- compute_feature_enabled = fake_compute_feature_enabled()
- volume_feature_enabled = fake_default_feature_enabled()
- network_feature_enabled = fake_default_feature_enabled()
- object_storage_feature_enabled = fake_object_storage_discoverable_apis()
-
- service_available = fake_service_available()
-
- compute = fake_compute()
- identity = fake_identity()
-
- negative = fake_negative()
+class FakePrivate(config.TempestConfigPrivate):
+ def __init__(self):
+ cfg.CONF([], default_config_files=[])
+ self._set_attrs()
diff --git a/tempest/tests/negative/test_negative_auto_test.py b/tempest/tests/negative/test_negative_auto_test.py
index 27ddc95..7a1909a 100644
--- a/tempest/tests/negative/test_negative_auto_test.py
+++ b/tempest/tests/negative/test_negative_auto_test.py
@@ -15,6 +15,7 @@
import mock
+from tempest import config
import tempest.test as test
from tempest.tests import base
from tempest.tests import fake_config
@@ -38,7 +39,8 @@
def setUp(self):
super(TestNegativeAutoTest, self).setUp()
- self.stubs.Set(test, 'CONF', fake_config.FakeConfig)
+ self.useFixture(fake_config.ConfigFixture())
+ self.stubs.Set(config, 'TempestConfigPrivate', fake_config.FakePrivate)
def _check_prop_entries(self, result, entry):
entries = [a for a in result if entry in a[0]]
diff --git a/tempest/tests/test_auth.py b/tempest/tests/test_auth.py
index df04d65..b6e15bd 100644
--- a/tempest/tests/test_auth.py
+++ b/tempest/tests/test_auth.py
@@ -42,7 +42,8 @@
def setUp(self):
super(BaseAuthTestsSetUp, self).setUp()
- self.stubs.Set(config, 'TempestConfigPrivate', fake_config.FakeConfig)
+ self.useFixture(fake_config.ConfigFixture())
+ self.stubs.Set(config, 'TempestConfigPrivate', fake_config.FakePrivate)
self.fake_http = fake_http.fake_httplib2(return_type=200)
self.stubs.Set(http.ClosingHttp, 'request', self.fake_http.request)
self.auth_provider = self._auth(self.credentials)
diff --git a/tempest/tests/test_decorators.py b/tempest/tests/test_decorators.py
index aa3c8fc..ebf0ca0 100644
--- a/tempest/tests/test_decorators.py
+++ b/tempest/tests/test_decorators.py
@@ -15,6 +15,9 @@
import testtools
+from oslo.config import cfg
+
+from tempest import config
from tempest import exceptions
from tempest.openstack.common.fixture import mockpatch
from tempest import test
@@ -25,7 +28,8 @@
class BaseDecoratorsTest(base.TestCase):
def setUp(self):
super(BaseDecoratorsTest, self).setUp()
- self.stubs.Set(test, 'CONF', fake_config.FakeConfig)
+ self.config_fixture = self.useFixture(fake_config.ConfigFixture())
+ self.stubs.Set(config, 'TempestConfigPrivate', fake_config.FakePrivate)
class TestAttrDecorator(BaseDecoratorsTest):
@@ -191,10 +195,8 @@
class TestRequiresExtDecorator(BaseDecoratorsTest):
def setUp(self):
super(TestRequiresExtDecorator, self).setUp()
- self.fixture = self.useFixture(mockpatch.PatchObject(
- test.CONF.compute_feature_enabled,
- 'api_extensions',
- new=['enabled_ext', 'another_ext']))
+ cfg.CONF.set_default('api_extensions', ['enabled_ext', 'another_ext'],
+ 'compute-feature-enabled')
def _test_requires_ext_helper(self, expected_to_skip=True,
**decorator_args):
@@ -220,7 +222,7 @@
def test_requires_ext_decorator_with_all_ext_enabled(self):
# disable fixture so the default (all) is used.
- self.fixture.cleanUp()
+ self.config_fixture.cleanUp()
self._test_requires_ext_helper(expected_to_skip=False,
extension='random_ext',
service='compute')
diff --git a/tempest/tests/test_rest_client.py b/tempest/tests/test_rest_client.py
index 9f07972..da9ab72 100644
--- a/tempest/tests/test_rest_client.py
+++ b/tempest/tests/test_rest_client.py
@@ -35,7 +35,8 @@
def setUp(self):
super(BaseRestClientTestClass, self).setUp()
- self.stubs.Set(config, 'TempestConfigPrivate', fake_config.FakeConfig)
+ self.useFixture(fake_config.ConfigFixture())
+ self.stubs.Set(config, 'TempestConfigPrivate', fake_config.FakePrivate)
self.rest_client = rest_client.RestClient(
fake_auth_provider.FakeAuthProvider())
self.stubs.Set(httplib2.Http, 'request', self.fake_http.request)
@@ -230,3 +231,156 @@
data = {"one_top_key": "not_list_or_dict_value"}
body = self.rest_client._parse_resp(json.dumps(data))
self.assertEqual(data, body)
+
+
+class TestRestClientErrorCheckerJSON(base.TestCase):
+ c_type = "application/json"
+
+ def set_data(self, r_code, enc=None, r_body=None):
+ if enc is None:
+ enc = self.c_type
+ resp_dict = {'status': r_code, 'content-type': enc}
+ resp = httplib2.Response(resp_dict)
+ data = {
+ "method": "fake_method",
+ "url": "fake_url",
+ "headers": "fake_headers",
+ "body": "fake_body",
+ "resp": resp,
+ "resp_body": '{"resp_body": "fake_resp_body"}',
+ }
+ if r_body is not None:
+ data.update({"resp_body": r_body})
+ return data
+
+ def setUp(self):
+ super(TestRestClientErrorCheckerJSON, self).setUp()
+ self.useFixture(fake_config.ConfigFixture())
+ self.stubs.Set(config, 'TempestConfigPrivate', fake_config.FakePrivate)
+ self.rest_client = rest_client.RestClient(
+ fake_auth_provider.FakeAuthProvider())
+
+ def test_response_less_than_400(self):
+ self.rest_client._error_checker(**self.set_data("399"))
+
+ def test_response_400(self):
+ self.assertRaises(exceptions.BadRequest,
+ self.rest_client._error_checker,
+ **self.set_data("400"))
+
+ def test_response_401(self):
+ self.assertRaises(exceptions.Unauthorized,
+ self.rest_client._error_checker,
+ **self.set_data("401"))
+
+ def test_response_403(self):
+ self.assertRaises(exceptions.Unauthorized,
+ self.rest_client._error_checker,
+ **self.set_data("403"))
+
+ def test_response_404(self):
+ self.assertRaises(exceptions.NotFound,
+ self.rest_client._error_checker,
+ **self.set_data("404"))
+
+ def test_response_409(self):
+ self.assertRaises(exceptions.Conflict,
+ self.rest_client._error_checker,
+ **self.set_data("409"))
+
+ def test_response_413(self):
+ self.assertRaises(exceptions.OverLimit,
+ self.rest_client._error_checker,
+ **self.set_data("413"))
+
+ def test_response_422(self):
+ self.assertRaises(exceptions.UnprocessableEntity,
+ self.rest_client._error_checker,
+ **self.set_data("422"))
+
+ def test_response_500_with_text(self):
+ # _parse_resp is expected to return 'str'
+ self.assertRaises(exceptions.ServerFault,
+ self.rest_client._error_checker,
+ **self.set_data("500"))
+
+ def test_response_501_with_text(self):
+ self.assertRaises(exceptions.ServerFault,
+ self.rest_client._error_checker,
+ **self.set_data("501"))
+
+ def test_response_500_with_dict(self):
+ r_body = '{"resp_body": {"err": "fake_resp_body"}}'
+ self.assertRaises(exceptions.ServerFault,
+ self.rest_client._error_checker,
+ **self.set_data("500", r_body=r_body))
+
+ def test_response_501_with_dict(self):
+ r_body = '{"resp_body": {"err": "fake_resp_body"}}'
+ self.assertRaises(exceptions.ServerFault,
+ self.rest_client._error_checker,
+ **self.set_data("501", r_body=r_body))
+
+ def test_response_bigger_than_400(self):
+ # Any response code, that bigger than 400, and not in
+ # (401, 403, 404, 409, 413, 422, 500, 501)
+ self.assertRaises(exceptions.UnexpectedResponseCode,
+ self.rest_client._error_checker,
+ **self.set_data("402"))
+
+
+class TestRestClientErrorCheckerXML(TestRestClientErrorCheckerJSON):
+ c_type = "application/xml"
+
+
+class TestRestClientErrorCheckerTEXT(TestRestClientErrorCheckerJSON):
+ c_type = "text/plain"
+
+ def test_fake_content_type(self):
+ # This test is required only in one exemplar
+ # Any response code, that bigger than 400, and not in
+ # (401, 403, 404, 409, 413, 422, 500, 501)
+ self.assertRaises(exceptions.InvalidContentType,
+ self.rest_client._error_checker,
+ **self.set_data("405", enc="fake_enc"))
+
+
+class TestRestClientUtils(BaseRestClientTestClass):
+
+ def _is_resource_deleted(self, resource_id):
+ if not isinstance(self.retry_pass, int):
+ return False
+ if self.retry_count >= self.retry_pass:
+ return True
+ self.retry_count = self.retry_count + 1
+ return False
+
+ def setUp(self):
+ self.fake_http = fake_http.fake_httplib2()
+ super(TestRestClientUtils, self).setUp()
+ self.retry_count = 0
+ self.retry_pass = None
+ self.original_deleted_method = self.rest_client.is_resource_deleted
+ self.rest_client.is_resource_deleted = self._is_resource_deleted
+
+ def test_wait_for_resource_deletion(self):
+ self.retry_pass = 2
+ # Ensure timeout long enough for loop execution to hit retry count
+ self.rest_client.build_timeout = 500
+ sleep_mock = self.patch('time.sleep')
+ self.rest_client.wait_for_resource_deletion('1234')
+ self.assertEqual(len(sleep_mock.mock_calls), 2)
+
+ def test_wait_for_resource_deletion_not_deleted(self):
+ self.patch('time.sleep')
+ # Set timeout to be very quick to force exception faster
+ self.rest_client.build_timeout = 1
+ self.assertRaises(exceptions.TimeoutException,
+ self.rest_client.wait_for_resource_deletion,
+ '1234')
+
+ def test_wait_for_deletion_with_unimplemented_deleted_method(self):
+ self.rest_client.is_resource_deleted = self.original_deleted_method
+ self.assertRaises(NotImplementedError,
+ self.rest_client.wait_for_resource_deletion,
+ '1234')
diff --git a/tempest/tests/test_tenant_isolation.py b/tempest/tests/test_tenant_isolation.py
new file mode 100644
index 0000000..2e50cfd
--- /dev/null
+++ b/tempest/tests/test_tenant_isolation.py
@@ -0,0 +1,336 @@
+# Copyright 2014 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 keystoneclient.v2_0.client as keystoneclient
+from mock import patch
+import neutronclient.v2_0.client as neutronclient
+from oslo.config import cfg
+
+from tempest.common import isolated_creds
+from tempest import config
+from tempest.openstack.common.fixture import mockpatch
+from tempest.services.identity.json import identity_client as json_iden_client
+from tempest.services.identity.xml import identity_client as xml_iden_client
+from tempest.services.network.json import network_client as json_network_client
+from tempest.services.network.xml import network_client as xml_network_client
+from tempest.tests import base
+from tempest.tests import fake_config
+
+
+class TestTenantIsolation(base.TestCase):
+
+ def setUp(self):
+ super(TestTenantIsolation, self).setUp()
+ self.useFixture(fake_config.ConfigFixture())
+ self.stubs.Set(config, 'TempestConfigPrivate', fake_config.FakePrivate)
+
+ def test_tempest_client(self):
+ iso_creds = isolated_creds.IsolatedCreds('test class')
+ self.assertTrue(isinstance(iso_creds.identity_admin_client,
+ json_iden_client.IdentityClientJSON))
+ self.assertTrue(isinstance(iso_creds.network_admin_client,
+ json_network_client.NetworkClientJSON))
+
+ def test_official_client(self):
+ self.useFixture(mockpatch.PatchObject(keystoneclient.Client,
+ 'authenticate'))
+ iso_creds = isolated_creds.IsolatedCreds('test class',
+ tempest_client=False)
+ self.assertTrue(isinstance(iso_creds.identity_admin_client,
+ keystoneclient.Client))
+ self.assertTrue(isinstance(iso_creds.network_admin_client,
+ neutronclient.Client))
+
+ def test_tempest_client_xml(self):
+ iso_creds = isolated_creds.IsolatedCreds('test class', interface='xml')
+ self.assertEqual(iso_creds.interface, 'xml')
+ self.assertTrue(isinstance(iso_creds.identity_admin_client,
+ xml_iden_client.IdentityClientXML))
+ self.assertTrue(isinstance(iso_creds.network_admin_client,
+ xml_network_client.NetworkClientXML))
+
+ def _mock_user_create(self, id, name):
+ user_fix = self.useFixture(mockpatch.PatchObject(
+ json_iden_client.IdentityClientJSON,
+ 'create_user',
+ return_value=({'status': 200},
+ {'id': id, 'name': name})))
+ return user_fix
+
+ def _mock_tenant_create(self, id, name):
+ tenant_fix = self.useFixture(mockpatch.PatchObject(
+ json_iden_client.IdentityClientJSON,
+ 'create_tenant',
+ return_value=({'status': 200},
+ {'id': id, 'name': name})))
+ return tenant_fix
+
+ def _mock_network_create(self, iso_creds, id, name):
+ net_fix = self.useFixture(mockpatch.PatchObject(
+ iso_creds.network_admin_client,
+ 'create_network',
+ return_value=({'status': 200},
+ {'network': {'id': id, 'name': name}})))
+ return net_fix
+
+ def _mock_subnet_create(self, iso_creds, id, name):
+ subnet_fix = self.useFixture(mockpatch.PatchObject(
+ iso_creds.network_admin_client,
+ 'create_subnet',
+ return_value=({'status': 200},
+ {'subnet': {'id': id, 'name': name}})))
+ return subnet_fix
+
+ def _mock_router_create(self, id, name):
+ router_fix = self.useFixture(mockpatch.PatchObject(
+ json_network_client.NetworkClientJSON,
+ 'create_router',
+ return_value=({'status': 200},
+ {'router': {'id': id, 'name': name}})))
+ return router_fix
+
+ @patch('tempest.common.rest_client.RestClient')
+ def test_primary_creds(self, MockRestClient):
+ cfg.CONF.set_default('neutron', False, 'service_available')
+ iso_creds = isolated_creds.IsolatedCreds('test class',
+ password='fake_password')
+ self._mock_tenant_create('1234', 'fake_prim_tenant')
+ self._mock_user_create('1234', 'fake_prim_user')
+ username, tenant_name, password = iso_creds.get_primary_creds()
+ self.assertEqual(username, 'fake_prim_user')
+ self.assertEqual(tenant_name, 'fake_prim_tenant')
+ # Verify helper methods
+ tenant = iso_creds.get_primary_tenant()
+ user = iso_creds.get_primary_user()
+ self.assertEqual(tenant['id'], '1234')
+ self.assertEqual(user['id'], '1234')
+
+ @patch('tempest.common.rest_client.RestClient')
+ def test_admin_creds(self, MockRestClient):
+ cfg.CONF.set_default('neutron', False, 'service_available')
+ iso_creds = isolated_creds.IsolatedCreds('test class',
+ password='fake_password')
+ self._mock_user_create('1234', 'fake_admin_user')
+ self._mock_tenant_create('1234', 'fake_admin_tenant')
+ self.useFixture(mockpatch.PatchObject(
+ json_iden_client.IdentityClientJSON,
+ 'list_roles',
+ return_value=({'status': 200},
+ [{'id': '1234', 'name': 'admin'}])))
+
+ user_mock = patch.object(json_iden_client.IdentityClientJSON,
+ 'assign_user_role')
+ user_mock.start()
+ self.addCleanup(user_mock.stop)
+ with patch.object(json_iden_client.IdentityClientJSON,
+ 'assign_user_role') as user_mock:
+ username, tenant_name, password = iso_creds.get_admin_creds()
+ user_mock.assert_called_once_with('1234', '1234', '1234')
+ self.assertEqual(username, 'fake_admin_user')
+ self.assertEqual(tenant_name, 'fake_admin_tenant')
+ # Verify helper methods
+ tenant = iso_creds.get_admin_tenant()
+ user = iso_creds.get_admin_user()
+ self.assertEqual(tenant['id'], '1234')
+ self.assertEqual(user['id'], '1234')
+
+ @patch('tempest.common.rest_client.RestClient')
+ def test_all_cred_cleanup(self, MockRestClient):
+ cfg.CONF.set_default('neutron', False, 'service_available')
+ iso_creds = isolated_creds.IsolatedCreds('test class',
+ password='fake_password')
+ tenant_fix = self._mock_tenant_create('1234', 'fake_prim_tenant')
+ user_fix = self._mock_user_create('1234', 'fake_prim_user')
+ username, tenant_name, password = iso_creds.get_primary_creds()
+ tenant_fix.cleanUp()
+ user_fix.cleanUp()
+ tenant_fix = self._mock_tenant_create('12345', 'fake_alt_tenant')
+ user_fix = self._mock_user_create('12345', 'fake_alt_user')
+ alt_username, alt_tenant, alt_password = iso_creds.get_alt_creds()
+ tenant_fix.cleanUp()
+ user_fix.cleanUp()
+ tenant_fix = self._mock_tenant_create('123456', 'fake_admin_tenant')
+ user_fix = self._mock_user_create('123456', 'fake_admin_user')
+ self.useFixture(mockpatch.PatchObject(
+ json_iden_client.IdentityClientJSON,
+ 'list_roles',
+ return_value=({'status': 200},
+ [{'id': '123456', 'name': 'admin'}])))
+ with patch.object(json_iden_client.IdentityClientJSON,
+ 'assign_user_role'):
+ admin_username, admin_tenant, admin_pass = \
+ iso_creds.get_admin_creds()
+ user_mock = self.patch(
+ 'tempest.services.identity.json.identity_client.'
+ 'IdentityClientJSON.delete_user')
+ tenant_mock = self.patch(
+ 'tempest.services.identity.json.identity_client.'
+ 'IdentityClientJSON.delete_tenant')
+ iso_creds.clear_isolated_creds()
+ # Verify user delete calls
+ calls = user_mock.mock_calls
+ self.assertEqual(len(calls), 3)
+ args = map(lambda x: x[1][0], calls)
+ self.assertIn('1234', args)
+ self.assertIn('12345', args)
+ self.assertIn('123456', args)
+ # Verify tenant delete calls
+ calls = tenant_mock.mock_calls
+ self.assertEqual(len(calls), 3)
+ args = map(lambda x: x[1][0], calls)
+ self.assertIn('1234', args)
+ self.assertIn('12345', args)
+ self.assertIn('123456', args)
+
+ @patch('tempest.common.rest_client.RestClient')
+ def test_alt_creds(self, MockRestClient):
+ cfg.CONF.set_default('neutron', False, 'service_available')
+ iso_creds = isolated_creds.IsolatedCreds('test class',
+ password='fake_password')
+ self._mock_user_create('1234', 'fake_alt_user')
+ self._mock_tenant_create('1234', 'fake_alt_tenant')
+ username, tenant_name, password = iso_creds.get_alt_creds()
+ self.assertEqual(username, 'fake_alt_user')
+ self.assertEqual(tenant_name, 'fake_alt_tenant')
+ # Verify helper methods
+ tenant = iso_creds.get_alt_tenant()
+ user = iso_creds.get_alt_user()
+ self.assertEqual(tenant['id'], '1234')
+ self.assertEqual(user['id'], '1234')
+
+ @patch('tempest.common.rest_client.RestClient')
+ def test_network_creation(self, MockRestClient):
+ iso_creds = isolated_creds.IsolatedCreds('test class',
+ password='fake_password')
+ self._mock_user_create('1234', 'fake_prim_user')
+ self._mock_tenant_create('1234', 'fake_prim_tenant')
+ self._mock_network_create(iso_creds, '1234', 'fake_net')
+ self._mock_subnet_create(iso_creds, '1234', 'fake_subnet')
+ self._mock_router_create('1234', 'fake_router')
+ router_interface_mock = self.patch(
+ 'tempest.services.network.json.network_client.NetworkClientJSON.'
+ 'add_router_interface_with_subnet_id')
+ username, tenant_name, password = iso_creds.get_primary_creds()
+ router_interface_mock.called_once_with('1234', '1234')
+ network = iso_creds.get_primary_network()
+ subnet = iso_creds.get_primary_subnet()
+ router = iso_creds.get_primary_router()
+ self.assertEqual(network['id'], '1234')
+ self.assertEqual(network['name'], 'fake_net')
+ self.assertEqual(subnet['id'], '1234')
+ self.assertEqual(subnet['name'], 'fake_subnet')
+ self.assertEqual(router['id'], '1234')
+ self.assertEqual(router['name'], 'fake_router')
+
+ @patch('tempest.common.rest_client.RestClient')
+ def test_network_cleanup(self, MockRestClient):
+ iso_creds = isolated_creds.IsolatedCreds('test class',
+ password='fake_password')
+ # Create primary tenant and network
+ user_fix = self._mock_user_create('1234', 'fake_prim_user')
+ tenant_fix = self._mock_tenant_create('1234', 'fake_prim_tenant')
+ net_fix = self._mock_network_create(iso_creds, '1234', 'fake_net')
+ subnet_fix = self._mock_subnet_create(iso_creds, '1234', 'fake_subnet')
+ router_fix = self._mock_router_create('1234', 'fake_router')
+ router_interface_mock = self.patch(
+ 'tempest.services.network.json.network_client.NetworkClientJSON.'
+ 'add_router_interface_with_subnet_id')
+ username, tenant_name, password = iso_creds.get_primary_creds()
+ router_interface_mock.called_once_with('1234', '1234')
+ router_interface_mock.reset_mock()
+ tenant_fix.cleanUp()
+ user_fix.cleanUp()
+ net_fix.cleanUp()
+ subnet_fix.cleanUp()
+ router_fix.cleanUp()
+ # Create alternate tenant and network
+ user_fix = self._mock_user_create('12345', 'fake_alt_user')
+ tenant_fix = self._mock_tenant_create('12345', 'fake_alt_tenant')
+ net_fix = self._mock_network_create(iso_creds, '12345', 'fake_alt_net')
+ subnet_fix = self._mock_subnet_create(iso_creds, '12345',
+ 'fake_alt_subnet')
+ router_fix = self._mock_router_create('12345', 'fake_alt_router')
+ alt_username, alt_tenant_name, password = iso_creds.get_alt_creds()
+ router_interface_mock.called_once_with('12345', '12345')
+ router_interface_mock.reset_mock()
+ tenant_fix.cleanUp()
+ user_fix.cleanUp()
+ net_fix.cleanUp()
+ subnet_fix.cleanUp()
+ router_fix.cleanUp()
+ # Create admin tenant and networks
+ user_fix = self._mock_user_create('123456', 'fake_admin_user')
+ tenant_fix = self._mock_tenant_create('123456', 'fake_admin_tenant')
+ net_fix = self._mock_network_create(iso_creds, '123456',
+ 'fake_admin_net')
+ subnet_fix = self._mock_subnet_create(iso_creds, '123456',
+ 'fake_admin_subnet')
+ router_fix = self._mock_router_create('123456', 'fake_admin_router')
+ self.useFixture(mockpatch.PatchObject(
+ json_iden_client.IdentityClientJSON,
+ 'list_roles',
+ return_value=({'status': 200},
+ [{'id': '123456', 'name': 'admin'}])))
+ with patch.object(json_iden_client.IdentityClientJSON,
+ 'assign_user_role'):
+ admin_user, admin_tenant, password = iso_creds.get_admin_creds()
+ self.patch('tempest.services.identity.json.identity_client.'
+ 'IdentityClientJSON.delete_user')
+ self.patch('tempest.services.identity.json.identity_client.'
+ 'IdentityClientJSON.delete_tenant')
+ net = patch.object(iso_creds.network_admin_client,
+ 'delete_network')
+ net_mock = net.start()
+ subnet = patch.object(iso_creds.network_admin_client,
+ 'delete_subnet')
+ subnet_mock = subnet.start()
+ router = patch.object(iso_creds.network_admin_client,
+ 'delete_router')
+ router_mock = router.start()
+ remove_router_interface_mock = self.patch(
+ 'tempest.services.network.json.network_client.NetworkClientJSON.'
+ 'remove_router_interface_with_subnet_id')
+ port_list_mock = patch.object(iso_creds.network_admin_client,
+ 'list_ports', return_value=(
+ {'status': 200}, {'ports': []}))
+ port_list_mock.start()
+ iso_creds.clear_isolated_creds()
+ # Verify remove router interface calls
+ calls = remove_router_interface_mock.mock_calls
+ self.assertEqual(len(calls), 3)
+ args = map(lambda x: x[1], calls)
+ self.assertIn(('1234', '1234'), args)
+ self.assertIn(('12345', '12345'), args)
+ self.assertIn(('123456', '123456'), args)
+ # Verify network delete calls
+ calls = net_mock.mock_calls
+ self.assertEqual(len(calls), 3)
+ args = map(lambda x: x[1][0], calls)
+ self.assertIn('1234', args)
+ self.assertIn('12345', args)
+ self.assertIn('123456', args)
+ # Verify subnet delete calls
+ calls = subnet_mock.mock_calls
+ self.assertEqual(len(calls), 3)
+ args = map(lambda x: x[1][0], calls)
+ self.assertIn('1234', args)
+ self.assertIn('12345', args)
+ self.assertIn('123456', args)
+ # Verify router delete calls
+ calls = router_mock.mock_calls
+ self.assertEqual(len(calls), 3)
+ args = map(lambda x: x[1][0], calls)
+ self.assertIn('1234', args)
+ self.assertIn('12345', args)
+ self.assertIn('123456', args)
diff --git a/tempest/tests/test_waiters.py b/tempest/tests/test_waiters.py
new file mode 100644
index 0000000..1f9825e
--- /dev/null
+++ b/tempest/tests/test_waiters.py
@@ -0,0 +1,49 @@
+# Copyright 2014 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 time
+
+import mock
+
+from tempest.common import waiters
+from tempest import exceptions
+from tempest.tests import base
+
+
+class TestImageWaiters(base.TestCase):
+ def setUp(self):
+ super(TestImageWaiters, self).setUp()
+ self.client = mock.MagicMock()
+ self.client.build_timeout = 1
+ self.client.build_interval = 1
+
+ def test_wait_for_image_status(self):
+ self.client.get_image.return_value = (None, {'status': 'active'})
+ start_time = int(time.time())
+ waiters.wait_for_image_status(self.client, 'fake_image_id', 'active')
+ end_time = int(time.time())
+ # Ensure waiter returns before build_timeout
+ self.assertTrue((end_time - start_time) < 10)
+
+ def test_wait_for_image_status_timeout(self):
+ self.client.get_image.return_value = (None, {'status': 'saving'})
+ self.assertRaises(exceptions.TimeoutException,
+ waiters.wait_for_image_status,
+ self.client, 'fake_image_id', 'active')
+
+ def test_wait_for_image_status_error_on_image_create(self):
+ self.client.get_image.return_value = (None, {'status': 'ERROR'})
+ self.assertRaises(exceptions.AddImageException,
+ waiters.wait_for_image_status,
+ self.client, 'fake_image_id', 'active')
diff --git a/tempest/thirdparty/boto/test.py b/tempest/thirdparty/boto/test.py
index 10d421e..4c39f78 100644
--- a/tempest/thirdparty/boto/test.py
+++ b/tempest/thirdparty/boto/test.py
@@ -108,6 +108,9 @@
CODE_RE = '.*' # regexp makes sense in group match
def match(self, exc):
+ """:returns: Retruns with an error string if not matches,
+ returns with None when matches.
+ """
if not isinstance(exc, exception.BotoServerError):
return "%r not an BotoServerError instance" % exc
LOG.info("Status: %s , error_code: %s", exc.status, exc.error_code)
@@ -119,6 +122,7 @@
return ("Error code (%s) does not match" +
"the expected re pattern \"%s\"") %\
(exc.error_code, self.CODE_RE)
+ return None
class ClientError(BotoExceptionMatcher):
@@ -313,7 +317,7 @@
except ValueError:
return "_GONE"
except exception.EC2ResponseError as exc:
- if colusure_matcher.match(exc):
+ if colusure_matcher.match(exc) is None:
return "_GONE"
else:
raise
@@ -449,7 +453,7 @@
return "_GONE"
except exception.EC2ResponseError as exc:
if cls.ec2_error_code.\
- client.InvalidInstanceID.NotFound.match(exc):
+ client.InvalidInstanceID.NotFound.match(exc) is None:
return "_GONE"
# NOTE(afazekas): incorrect code,
# but the resource must be destoreyd
diff --git a/tempest/thirdparty/boto/test_ec2_instance_run.py b/tempest/thirdparty/boto/test_ec2_instance_run.py
index bbfbb79..e6a1638 100644
--- a/tempest/thirdparty/boto/test_ec2_instance_run.py
+++ b/tempest/thirdparty/boto/test_ec2_instance_run.py
@@ -40,7 +40,7 @@
": requires ami/aki/ari manifest")))
cls.s3_client = cls.os.s3_client
cls.ec2_client = cls.os.ec2api_client
- cls.zone = cls.ec2_client.get_good_zone()
+ cls.zone = CONF.boto.aws_zone
cls.materials_path = CONF.boto.s3_materials_path
ami_manifest = CONF.boto.ami_manifest
aki_manifest = CONF.boto.aki_manifest
diff --git a/tempest/thirdparty/boto/test_ec2_volumes.py b/tempest/thirdparty/boto/test_ec2_volumes.py
index 6a771e5..12dea18 100644
--- a/tempest/thirdparty/boto/test_ec2_volumes.py
+++ b/tempest/thirdparty/boto/test_ec2_volumes.py
@@ -38,7 +38,7 @@
raise cls.skipException(skip_msg)
cls.client = cls.os.ec2api_client
- cls.zone = cls.client.get_good_zone()
+ cls.zone = CONF.boto.aws_zone
@test.attr(type='smoke')
def test_create_get_delete(self):
diff --git a/tools/check_logs.py b/tools/check_logs.py
index 98e079a..e28c230 100755
--- a/tools/check_logs.py
+++ b/tools/check_logs.py
@@ -25,20 +25,45 @@
import yaml
-is_neutron = os.environ.get('DEVSTACK_GATE_NEUTRON', "0") == "1"
is_grenade = (os.environ.get('DEVSTACK_GATE_GRENADE', "0") == "1" or
os.environ.get('DEVSTACK_GATE_GRENADE_FORWARD', "0") == "1")
dump_all_errors = True
+# As logs are made clean, add to this set
+allowed_dirty = set([
+ 'c-api',
+ 'ceilometer-acentral',
+ 'ceilometer-acompute',
+ 'ceilometer-alarm-evaluator',
+ 'ceilometer-anotification',
+ 'ceilometer-api',
+ 'c-vol',
+ 'g-api',
+ 'h-api',
+ 'h-eng',
+ 'ir-cond',
+ 'n-api',
+ 'n-cpu',
+ 'n-net',
+ 'n-sch',
+ 'q-agt',
+ 'q-dhcp',
+ 'q-lbaas',
+ 'q-meta',
+ 'q-metering',
+ 'q-svc',
+ 'q-vpn',
+ 's-proxy'])
+
def process_files(file_specs, url_specs, whitelists):
regexp = re.compile(r"^.* (ERROR|CRITICAL|TRACE) .*\[.*\-.*\]")
- had_errors = False
+ logs_with_errors = []
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
+ logs_with_errors.append(name)
for (name, url) in url_specs:
whitelist = whitelists.get(name, [])
req = urllib2.Request(url)
@@ -47,8 +72,8 @@
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
+ logs_with_errors.append(name)
+ return logs_with_errors
def scan_content(name, content, regexp, whitelist):
@@ -65,12 +90,12 @@
break
if not whitelisted or dump_all_errors:
if print_log_name:
- print("Log File: %s" % name)
+ print("Log File Has Errors: %s" % name)
print_log_name = False
if not whitelisted:
had_errors = True
print("*** Not Whitelisted ***"),
- print(line)
+ print(line.rstrip())
return had_errors
@@ -122,19 +147,22 @@
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):
+ logs_with_errors = process_files(files_to_process, urls_to_process,
+ whitelists)
+ if logs_with_errors:
print("Logs have errors")
- if is_neutron:
- print("Currently not failing neutron builds with errors")
- return 0
- if is_grenade:
- print("Currently not failing grenade runs with errors")
- return 0
- print("FAILED")
- return 1
- else:
- print("ok")
+ if is_grenade:
+ print("Currently not failing grenade runs with errors")
return 0
+ failed = False
+ for log in logs_with_errors:
+ if log not in allowed_dirty:
+ print("Log: %s not allowed to have ERRORS or TRACES" % log)
+ failed = True
+ if failed:
+ return 1
+ print("ok")
+ return 0
usage = """
Find non-white-listed log errors in log files from a devstack-gate run.
diff --git a/tools/verify_tempest_config.py b/tools/verify_tempest_config.py
index 4be812c..aa92c0b 100755
--- a/tools/verify_tempest_config.py
+++ b/tools/verify_tempest_config.py
@@ -63,6 +63,7 @@
'nova_v3': os.extensions_v3_client,
'cinder': os.volumes_extension_client,
'neutron': os.network_client,
+ 'swift': os.account_client,
}
if service not in extensions_client:
print('No tempest extensions client for %s' % service)
@@ -76,6 +77,7 @@
'nova_v3': CONF.compute_feature_enabled.api_v3_extensions,
'cinder': CONF.volume_feature_enabled.api_extensions,
'neutron': CONF.network_feature_enabled.api_extensions,
+ 'swift': CONF.object_storage_feature_enabled.discoverable_apis,
}
if service not in extensions_options:
print('No supported extensions list option for %s' % service)
@@ -93,6 +95,10 @@
# instead of name.
if service == 'neutron':
extensions = map(lambda x: x['alias'], resp['extensions'])
+ elif service == 'swift':
+ # Remove Swift general information from extensions list
+ resp.pop('swift')
+ extensions = resp.keys()
else:
extensions = map(lambda x: x['name'], resp['extensions'])
@@ -132,21 +138,63 @@
"enabled extensions" % (service, extension))
-def check_service_availability(service):
- if service == 'nova_v3':
- service = 'nova'
- return getattr(CONF.service_available, service)
+def check_service_availability(os):
+ services = []
+ avail_services = []
+ codename_match = {
+ 'volume': 'cinder',
+ 'network': 'neutron',
+ 'image': 'glance',
+ 'object_storage': 'swift',
+ 'compute': 'nova',
+ 'orchestration': 'heat',
+ 'metering': 'ceilometer',
+ 'telemetry': 'ceilometer',
+ 'data_processing': 'savanna',
+ 'baremetal': 'ironic',
+ 'identity': 'keystone'
+
+ }
+ # Get catalog list for endpoints to use for validation
+ __, endpoints = os.endpoints_client.list_endpoints()
+ for endpoint in endpoints:
+ __, service = os.service_client.get_service(endpoint['service_id'])
+ services.append(service['type'])
+ # Pull all catalog types from config file and compare against endpoint list
+ for cfgname in dir(CONF._config):
+ cfg = getattr(CONF, cfgname)
+ catalog_type = getattr(cfg, 'catalog_type', None)
+ if not catalog_type:
+ continue
+ else:
+ if cfgname == 'identity':
+ # Keystone is a required service for tempest
+ continue
+ if catalog_type not in services:
+ if getattr(CONF.service_available, codename_match[cfgname]):
+ print('Endpoint type %s not found either disable service '
+ '%s or fix the catalog_type in the config file' % (
+ catalog_type, codename_match[cfgname]))
+ else:
+ if not getattr(CONF.service_available,
+ codename_match[cfgname]):
+ print('Endpoint type %s is available, service %s should be'
+ ' set as available in the config file.' % (
+ catalog_type, codename_match[cfgname]))
+ else:
+ avail_services.append(codename_match[cfgname])
+ return avail_services
def main(argv):
print('Running config verification...')
os = clients.ComputeAdminManager(interface='json')
+ services = check_service_availability(os)
results = {}
- for service in ['nova', 'nova_v3', 'cinder', 'neutron']:
- # TODO(mtreinish) make this a keystone endpoint check for available
- # services
- if not check_service_availability(service):
- print("%s is not available" % service)
+ for service in ['nova', 'nova_v3', 'cinder', 'neutron', 'swift']:
+ if service == 'nova_v3' and 'nova' not in services:
+ continue
+ elif service not in services:
continue
results = verify_extensions(os, service, results)
verify_glance_api_versions(os)