Merge "Update to latest tox"
diff --git a/HACKING.rst b/HACKING.rst
index 7871f60..7d995c3 100644
--- a/HACKING.rst
+++ b/HACKING.rst
@@ -105,6 +105,19 @@
in tempest.api.compute would require a service tag for those services, however
they do not need to be tagged as compute.
+Negative Tests
+--------------
+When adding negative tests to tempest there are 2 requirements. First the tests
+must be marked with a negative attribute. For example::
+
+ @attr(type=negative)
+ def test_resource_no_uuid(self):
+ ...
+
+The second requirement is that all negative tests must be added to a negative
+test file. If such a file doesn't exist for the particular resource being
+tested a new test file should be added.
+
Test skips because of Known Bugs
--------------------------------
diff --git a/etc/whitelist.yaml b/etc/whitelist.yaml
new file mode 100644
index 0000000..6762f9f
--- /dev/null
+++ b/etc/whitelist.yaml
@@ -0,0 +1,131 @@
+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"
+
+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"
+
+ceilometer-acompute:
+ - module: "ceilometer.compute.pollsters.disk"
+ message: "Requested operation is not valid: domain is not running"
+ - module: "ceilometer.compute.pollsters.disk"
+ message: "Domain not found: no domain with matching uuid"
+
+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':"
+
+n-cond:
+ - module: "nova.notifications"
+ message: "Failed to send state update notification"
+
+n-sch:
+ - module: "nova.scheduler.filter_scheduler"
+ message: "Error from last host: "
+
+c-api:
+ - module: "cinder.api.middleware.fault"
+ message: "Caught error: Volume .* could not be found"
+
+q-dhpc:
+ - module: "neutron.common.legacy"
+ message: "Skipping unknown group key: firewall_driver"
+ - module: "neutron.agent.dhcp_agent"
+ message: "Unable to enable dhcp"
+ - module: "neutron.agent.dhcp_agent"
+ message: " Network .* RPC info call failed"
+
+ceilometer-collector:
+ - module: "stevedore.extension"
+ message: ".*"
+ - module: "ceilometer.collector.dispatcher.database"
+ message: "duplicate key value violates unique constraint"
+
+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-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 .* could not be found"
+ - module: "neutron.openstack.common.rpc.common"
+ message: "Pool .* could not be found"
+ - module: "neutron.api.v2.resource"
+ message: "show failed"
+
diff --git a/tempest/api/compute/admin/test_fixed_ips.py b/tempest/api/compute/admin/test_fixed_ips.py
index 85b03e6..ee262df 100644
--- a/tempest/api/compute/admin/test_fixed_ips.py
+++ b/tempest/api/compute/admin/test_fixed_ips.py
@@ -16,7 +16,6 @@
# under the License.
from tempest.api.compute import base
-from tempest import config
from tempest import exceptions
from tempest.test import attr
@@ -24,8 +23,6 @@
class FixedIPsTestJson(base.BaseComputeAdminTest):
_interface = 'json'
- CONF = config.TempestConfig()
-
@classmethod
def setUpClass(cls):
super(FixedIPsTestJson, cls).setUpClass()
diff --git a/tempest/api/compute/admin/test_flavors_access.py b/tempest/api/compute/admin/test_flavors_access.py
index 107b23d..8213839 100644
--- a/tempest/api/compute/admin/test_flavors_access.py
+++ b/tempest/api/compute/admin/test_flavors_access.py
@@ -15,6 +15,8 @@
# License for the specific language governing permissions and limitations
# under the License.
+import uuid
+
from tempest.api import compute
from tempest.api.compute import base
from tempest.common.utils.data_utils import rand_int_id
@@ -123,6 +125,48 @@
new_flavor['id'],
self.tenant_id)
+ @attr(type=['negative', 'gate'])
+ def test_add_flavor_access_duplicate(self):
+ # Create a new flavor.
+ flavor_name = rand_name(self.flavor_name_prefix)
+ new_flavor_id = rand_int_id(start=1000)
+ resp, new_flavor = self.client.create_flavor(flavor_name,
+ self.ram, self.vcpus,
+ self.disk,
+ new_flavor_id,
+ is_public='False')
+ self.addCleanup(self.client.delete_flavor, new_flavor['id'])
+
+ # Add flavor access to a tenant.
+ self.client.add_flavor_access(new_flavor['id'], self.tenant_id)
+ self.addCleanup(self.client.remove_flavor_access,
+ new_flavor['id'], self.tenant_id)
+
+ # An exception should be raised when adding flavor access to the same
+ # tenant
+ self.assertRaises(exceptions.Duplicate,
+ self.client.add_flavor_access,
+ new_flavor['id'],
+ self.tenant_id)
+
+ @attr(type=['negative', 'gate'])
+ def test_remove_flavor_access_not_found(self):
+ # Create a new flavor.
+ flavor_name = rand_name(self.flavor_name_prefix)
+ new_flavor_id = rand_int_id(start=1000)
+ resp, new_flavor = self.client.create_flavor(flavor_name,
+ self.ram, self.vcpus,
+ self.disk,
+ new_flavor_id,
+ is_public='False')
+ self.addCleanup(self.client.delete_flavor, new_flavor['id'])
+
+ # An exception should be raised when flavor access is not found
+ self.assertRaises(exceptions.NotFound,
+ self.client.remove_flavor_access,
+ new_flavor['id'],
+ str(uuid.uuid4()))
+
class FlavorsAdminTestXML(FlavorsAccessTestJSON):
_interface = 'xml'
diff --git a/tempest/api/compute/admin/test_flavors_extra_specs.py b/tempest/api/compute/admin/test_flavors_extra_specs.py
index ce326a3..fb6a463 100644
--- a/tempest/api/compute/admin/test_flavors_extra_specs.py
+++ b/tempest/api/compute/admin/test_flavors_extra_specs.py
@@ -115,6 +115,14 @@
self.flavor['id'],
'key1')
+ @attr(type=['negative', 'gate'])
+ def test_flavor_unset_nonexistent_key(self):
+ nonexistent_key = rand_name('flavor_key')
+ self.assertRaises(exceptions.NotFound,
+ self.client.unset_flavor_extra_spec,
+ self.flavor['id'],
+ nonexistent_key)
+
class FlavorsExtraSpecsTestXML(FlavorsExtraSpecsTestJSON):
_interface = 'xml'
diff --git a/tempest/api/compute/admin/test_servers.py b/tempest/api/compute/admin/test_servers.py
index e730d31..1b346f1 100644
--- a/tempest/api/compute/admin/test_servers.py
+++ b/tempest/api/compute/admin/test_servers.py
@@ -33,10 +33,10 @@
def setUpClass(cls):
super(ServersAdminTestJSON, cls).setUpClass()
cls.client = cls.os_adm.servers_client
+ cls.non_adm_client = cls.servers_client
cls.flavors_client = cls.os_adm.flavors_client
-
- cls.admin_client = cls._get_identity_admin_client()
- tenant = cls.admin_client.get_tenant_by_name(
+ cls.identity_client = cls._get_identity_admin_client()
+ tenant = cls.identity_client.get_tenant_by_name(
cls.client.tenant_name)
cls.tenant_id = tenant['id']
@@ -158,6 +158,24 @@
self.assertRaises(exceptions.NotFound,
self.client.reset_state, '999')
+ @attr(type='gate')
+ def test_get_server_diagnostics_by_admin(self):
+ # Retrieve server diagnostics by admin user
+ resp, diagnostic = self.client.get_server_diagnostics(self.s1_id)
+ self.assertEqual(200, resp.status)
+ basic_attrs = ['rx_packets', 'rx_errors', 'rx_drop',
+ 'tx_packets', 'tx_errors', 'tx_drop',
+ 'read_req', 'write_req', 'cpu', 'memory']
+ for key in basic_attrs:
+ self.assertIn(key, str(diagnostic.keys()))
+
+ @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)
+
class ServersAdminTestXML(ServersAdminTestJSON):
_interface = 'xml'
diff --git a/tempest/api/compute/base.py b/tempest/api/compute/base.py
index 0b527d9..885adcf 100644
--- a/tempest/api/compute/base.py
+++ b/tempest/api/compute/base.py
@@ -100,6 +100,19 @@
pass
@classmethod
+ def rebuild_server(cls, **kwargs):
+ # Destroy an existing server and creates a new one
+ try:
+ cls.servers_client.delete_server(cls.server_id)
+ cls.servers_client.wait_for_server_termination(cls.server_id)
+ except Exception as exc:
+ LOG.exception(exc)
+ pass
+ resp, server = cls.create_server(wait_until='ACTIVE', **kwargs)
+ cls.server_id = server['id']
+ cls.password = server['adminPass']
+
+ @classmethod
def clear_images(cls):
for image_id in cls.images:
try:
diff --git a/tempest/api/compute/servers/test_list_server_filters.py b/tempest/api/compute/servers/test_list_server_filters.py
index c469827..8e95671 100644
--- a/tempest/api/compute/servers/test_list_server_filters.py
+++ b/tempest/api/compute/servers/test_list_server_filters.py
@@ -57,32 +57,26 @@
raise RuntimeError("Image %s (image_ref_alt) was not found!" %
cls.image_ref_alt)
- cls.s1_name = rand_name('server')
- resp, cls.s1 = cls.client.create_server(cls.s1_name, cls.image_ref,
- cls.flavor_ref)
- cls.s2_name = rand_name('server')
- resp, cls.s2 = cls.client.create_server(cls.s2_name, cls.image_ref_alt,
- cls.flavor_ref)
- cls.s3_name = rand_name('server')
- resp, cls.s3 = cls.client.create_server(cls.s3_name, cls.image_ref,
- cls.flavor_ref_alt)
+ cls.s1_name = rand_name(cls.__name__ + '-instance')
+ resp, cls.s1 = cls.create_server(name=cls.s1_name,
+ image_id=cls.image_ref,
+ flavor=cls.flavor_ref,
+ wait_until='ACTIVE')
- cls.client.wait_for_server_status(cls.s1['id'], 'ACTIVE')
- resp, cls.s1 = cls.client.get_server(cls.s1['id'])
- cls.client.wait_for_server_status(cls.s2['id'], 'ACTIVE')
- resp, cls.s2 = cls.client.get_server(cls.s2['id'])
- cls.client.wait_for_server_status(cls.s3['id'], 'ACTIVE')
- resp, cls.s3 = cls.client.get_server(cls.s3['id'])
+ cls.s2_name = rand_name(cls.__name__ + '-instance')
+ resp, cls.s2 = cls.create_server(name=cls.s2_name,
+ image_id=cls.image_ref_alt,
+ flavor=cls.flavor_ref,
+ wait_until='ACTIVE')
+
+ cls.s3_name = rand_name(cls.__name__ + '-instance')
+ resp, cls.s3 = cls.create_server(name=cls.s3_name,
+ image_id=cls.image_ref,
+ flavor=cls.flavor_ref_alt,
+ wait_until='ACTIVE')
cls.fixed_network_name = cls.config.compute.fixed_network_name
- @classmethod
- def tearDownClass(cls):
- cls.client.delete_server(cls.s1['id'])
- cls.client.delete_server(cls.s2['id'])
- cls.client.delete_server(cls.s3['id'])
- super(ListServerFiltersTestJSON, cls).tearDownClass()
-
@utils.skip_unless_attr('multiple_images', 'Only one image found')
@attr(type='gate')
def test_list_servers_filter_by_image(self):
@@ -184,8 +178,8 @@
@attr(type='gate')
def test_list_servers_filtered_by_name_wildcard(self):
- # List all servers that contains 'server' in name
- params = {'name': 'server'}
+ # List all servers that contains '-instance' in name
+ params = {'name': '-instance'}
resp, body = self.client.list_servers(params)
servers = body['servers']
@@ -209,6 +203,7 @@
def test_list_servers_filtered_by_ip(self):
# Filter servers by ip
# Here should be listed 1 server
+ resp, self.s1 = self.client.get_server(self.s1['id'])
ip = self.s1['addresses'][self.fixed_network_name][0]['addr']
params = {'ip': ip}
resp, body = self.client.list_servers(params)
@@ -225,6 +220,7 @@
# Filter servers by regex ip
# List all servers filtered by part of ip address.
# Here should be listed all servers
+ resp, self.s1 = self.client.get_server(self.s1['id'])
ip = self.s1['addresses'][self.fixed_network_name][0]['addr'][0:-3]
params = {'ip': ip}
resp, body = self.client.list_servers(params)
diff --git a/tempest/api/compute/servers/test_list_servers_negative.py b/tempest/api/compute/servers/test_list_servers_negative.py
index 983258d..9dd2e27 100644
--- a/tempest/api/compute/servers/test_list_servers_negative.py
+++ b/tempest/api/compute/servers/test_list_servers_negative.py
@@ -37,7 +37,7 @@
blocks while the servers are being deleted.
"""
if len(servers):
- if not compute.MULTI_USER:
+ if not cls.config.compute.allow_tenant_isolation:
for srv in servers:
cls.client.wait_for_server_termination(srv['id'],
ignore_error=True)
diff --git a/tempest/api/compute/servers/test_server_actions.py b/tempest/api/compute/servers/test_server_actions.py
index e9defe5..5b46792 100644
--- a/tempest/api/compute/servers/test_server_actions.py
+++ b/tempest/api/compute/servers/test_server_actions.py
@@ -44,13 +44,13 @@
self.client.wait_for_server_status(self.server_id, 'ACTIVE')
except Exception:
# Rebuild server if something happened to it during a test
- self.rebuild_servers()
+ self.rebuild_server()
@classmethod
def setUpClass(cls):
super(ServerActionsTestJSON, cls).setUpClass()
cls.client = cls.servers_client
- cls.rebuild_servers()
+ cls.rebuild_server()
@testtools.skipUnless(compute.CHANGE_PASSWORD_AVAILABLE,
'Change password not available.')
@@ -118,7 +118,8 @@
password = 'rebuildPassw0rd'
resp, rebuilt_server = self.client.rebuild(self.server_id,
self.image_ref_alt,
- name=new_name, meta=meta,
+ name=new_name,
+ metadata=meta,
personality=personality,
adminPass=password)
@@ -226,7 +227,8 @@
self.assertRaises(exceptions.NotFound,
self.client.rebuild,
999, self.image_ref_alt,
- name=new_name, meta=meta,
+ name=new_name,
+ metadata=meta,
personality=personality,
adminPass='rebuild')
@@ -284,14 +286,6 @@
self.assertEqual(202, resp.status)
self.client.wait_for_server_status(self.server_id, 'ACTIVE')
- @classmethod
- def rebuild_servers(cls):
- # Destroy any existing server and creates a new one
- cls.clear_servers()
- resp, server = cls.create_server(wait_until='ACTIVE')
- cls.server_id = server['id']
- cls.password = server['adminPass']
-
@attr(type='gate')
def test_stop_start_server(self):
resp, server = self.servers_client.stop(self.server_id)
diff --git a/tempest/api/compute/servers/test_servers_negative.py b/tempest/api/compute/servers/test_servers_negative.py
index 5d9a5ce..c896224 100644
--- a/tempest/api/compute/servers/test_servers_negative.py
+++ b/tempest/api/compute/servers/test_servers_negative.py
@@ -28,6 +28,13 @@
class ServersNegativeTestJSON(base.BaseComputeTest):
_interface = 'json'
+ def setUp(self):
+ super(ServersNegativeTestJSON, self).setUp()
+ try:
+ self.client.wait_for_server_status(self.server_id, 'ACTIVE')
+ except Exception:
+ self.rebuild_server()
+
@classmethod
def setUpClass(cls):
super(ServersNegativeTestJSON, cls).setUpClass()
@@ -35,6 +42,8 @@
cls.img_client = cls.images_client
cls.alt_os = clients.AltManager()
cls.alt_client = cls.alt_os.servers_client
+ resp, server = cls.create_server(wait_until='ACTIVE')
+ cls.server_id = server['id']
@attr(type=['negative', 'gate'])
def test_server_name_blank(self):
@@ -92,8 +101,6 @@
@attr(type=['negative', 'gate'])
def test_reboot_deleted_server(self):
# Reboot a deleted server
- resp, server = self.create_server()
- self.server_id = server['id']
self.client.delete_server(self.server_id)
self.client.wait_for_server_termination(self.server_id)
self.assertRaises(exceptions.NotFound, self.client.reboot,
@@ -102,8 +109,6 @@
@attr(type=['negative', 'gate'])
def test_pause_paused_server(self):
# Pause a paused server.
- resp, server = self.create_server(wait_until='ACTIVE')
- self.server_id = server['id']
self.client.pause_server(self.server_id)
self.client.wait_for_server_status(self.server_id, 'PAUSED')
self.assertRaises(exceptions.Duplicate,
@@ -113,9 +118,6 @@
@attr(type=['negative', 'gate'])
def test_rebuild_deleted_server(self):
# Rebuild a deleted server
-
- resp, server = self.create_server()
- self.server_id = server['id']
self.client.delete_server(self.server_id)
self.client.wait_for_server_termination(self.server_id)
@@ -195,21 +197,19 @@
def test_update_server_of_another_tenant(self):
# Update name of a server that belongs to another tenant
- resp, server = self.create_server(wait_until='ACTIVE')
- new_name = server['id'] + '_new'
+ new_name = self.server_id + '_new'
self.assertRaises(exceptions.NotFound,
- self.alt_client.update_server, server['id'],
+ self.alt_client.update_server, self.server_id,
name=new_name)
@attr(type=['negative', 'gate'])
def test_update_server_name_length_exceeds_256(self):
# Update name of server exceed the name length limit
- resp, server = self.create_server(wait_until='ACTIVE')
new_name = 'a' * 256
self.assertRaises(exceptions.BadRequest,
self.client.update_server,
- server['id'],
+ self.server_id,
name=new_name)
@attr(type=['negative', 'gate'])
@@ -222,10 +222,9 @@
@attr(type=['negative', 'gate'])
def test_delete_a_server_of_another_tenant(self):
# Delete a server that belongs to another tenant
- resp, server = self.create_server(wait_until='ACTIVE')
self.assertRaises(exceptions.NotFound,
self.alt_client.delete_server,
- server['id'])
+ self.server_id)
@attr(type=['negative', 'gate'])
def test_delete_server_pass_negative_id(self):
@@ -277,11 +276,9 @@
@attr(type=['negative', 'gate'])
def test_unpause_server_invalid_state(self):
# unpause an active server.
- resp, server = self.create_server(wait_until='ACTIVE')
- server_id = server['id']
self.assertRaises(exceptions.Duplicate,
self.client.unpause_server,
- server_id)
+ self.server_id)
@attr(type=['negative', 'gate'])
def test_suspend_non_existent_server(self):
@@ -291,17 +288,13 @@
@attr(type=['negative', 'gate'])
def test_suspend_server_invalid_state(self):
- # create server.
- resp, server = self.create_server(wait_until='ACTIVE')
- server_id = server['id']
-
# suspend a suspended server.
- resp, _ = self.client.suspend_server(server_id)
+ resp, _ = self.client.suspend_server(self.server_id)
self.assertEqual(202, resp.status)
- self.client.wait_for_server_status(server_id, 'SUSPENDED')
+ self.client.wait_for_server_status(self.server_id, 'SUSPENDED')
self.assertRaises(exceptions.Duplicate,
self.client.suspend_server,
- server_id)
+ self.server_id)
@attr(type=['negative', 'gate'])
def test_resume_non_existent_server(self):
@@ -311,14 +304,10 @@
@attr(type=['negative', 'gate'])
def test_resume_server_invalid_state(self):
- # create server.
- resp, server = self.create_server(wait_until='ACTIVE')
- server_id = server['id']
-
# resume an active server.
self.assertRaises(exceptions.Duplicate,
self.client.resume_server,
- server_id)
+ self.server_id)
class ServersNegativeTestXML(ServersNegativeTestJSON):
diff --git a/tempest/api/identity/admin/test_users.py b/tempest/api/identity/admin/test_users.py
index 689ab29..66d35cb 100644
--- a/tempest/api/identity/admin/test_users.py
+++ b/tempest/api/identity/admin/test_users.py
@@ -19,7 +19,6 @@
from tempest.api.identity import base
from tempest.common.utils.data_utils import rand_name
-from tempest import exceptions
from tempest.test import attr
@@ -46,60 +45,19 @@
self.assertEqual('200', resp['status'])
self.assertEqual(self.alt_user, user['name'])
- @attr(type=['negative', 'gate'])
- def test_create_user_by_unauthorized_user(self):
- # Non-administrator should not be authorized to create a user
+ @attr(type='smoke')
+ def test_create_user_with_enabled(self):
+ # Create a user with enabled : False
self.data.setup_test_tenant()
- self.assertRaises(exceptions.Unauthorized,
- self.non_admin_client.create_user, self.alt_user,
- self.alt_password, self.data.tenant['id'],
- self.alt_email)
-
- @attr(type=['negative', 'gate'])
- def test_create_user_with_empty_name(self):
- # User with an empty name should not be created
- self.data.setup_test_tenant()
- self.assertRaises(exceptions.BadRequest, self.client.create_user, '',
- self.alt_password, self.data.tenant['id'],
- self.alt_email)
-
- @attr(type=['negative', 'gate'])
- def test_create_user_with_name_length_over_255(self):
- # Length of user name filed should be restricted to 255 characters
- self.data.setup_test_tenant()
- self.assertRaises(exceptions.BadRequest, self.client.create_user,
- 'a' * 256, self.alt_password,
- self.data.tenant['id'], self.alt_email)
-
- @attr(type=['negative', 'gate'])
- def test_create_user_with_duplicate_name(self):
- # Duplicate user should not be created
- self.data.setup_test_user()
- self.assertRaises(exceptions.Duplicate, self.client.create_user,
- self.data.test_user, self.data.test_password,
- self.data.tenant['id'], self.data.test_email)
-
- @attr(type=['negative', 'gate'])
- def test_create_user_for_non_existant_tenant(self):
- # Attempt to create a user in a non-existent tenant should fail
- self.assertRaises(exceptions.NotFound, self.client.create_user,
- self.alt_user, self.alt_password, '49ffgg99999',
- self.alt_email)
-
- @attr(type=['negative', 'gate'])
- def test_create_user_request_without_a_token(self):
- # Request to create a user without a valid token should fail
- self.data.setup_test_tenant()
- # Get the token of the current client
- token = self.client.get_auth()
- # Delete the token from database
- self.client.delete_token(token)
- self.assertRaises(exceptions.Unauthorized, self.client.create_user,
- self.alt_user, self.alt_password,
- self.data.tenant['id'], self.alt_email)
-
- # Unset the token to allow further tests to generate a new token
- self.client.clear_auth()
+ name = rand_name('test_user_')
+ resp, user = self.client.create_user(name, self.alt_password,
+ self.data.tenant['id'],
+ self.alt_email, enabled=False)
+ self.data.users.append(user)
+ self.assertEqual('200', resp['status'])
+ self.assertEqual(name, user['name'])
+ self.assertEqual('false', str(user['enabled']).lower())
+ self.assertEqual(self.alt_email, user['email'])
@attr(type='smoke')
def test_update_user(self):
@@ -141,20 +99,6 @@
resp, body = self.client.delete_user(user['id'])
self.assertEqual('204', resp['status'])
- @attr(type=['negative', 'gate'])
- def test_delete_users_by_unauthorized_user(self):
- # Non-administrator user should not be authorized to delete a user
- self.data.setup_test_user()
- self.assertRaises(exceptions.Unauthorized,
- self.non_admin_client.delete_user,
- self.data.user['id'])
-
- @attr(type=['negative', 'gate'])
- def test_delete_non_existant_user(self):
- # Attempt to delete a non-existent user should fail
- self.assertRaises(exceptions.NotFound, self.client.delete_user,
- 'junk12345123')
-
@attr(type='smoke')
def test_user_authentication(self):
# Valid user's token is authenticated
@@ -168,51 +112,6 @@
self.data.test_tenant)
self.assertEqual('200', resp['status'])
- @attr(type=['negative', 'gate'])
- def test_authentication_for_disabled_user(self):
- # Disabled user's token should not get authenticated
- self.data.setup_test_user()
- self.disable_user(self.data.test_user)
- self.assertRaises(exceptions.Unauthorized, self.token_client.auth,
- self.data.test_user,
- self.data.test_password,
- self.data.test_tenant)
-
- @attr(type=['negative', 'gate'])
- def test_authentication_when_tenant_is_disabled(self):
- # User's token for a disabled tenant should not be authenticated
- self.data.setup_test_user()
- self.disable_tenant(self.data.test_tenant)
- self.assertRaises(exceptions.Unauthorized, self.token_client.auth,
- self.data.test_user,
- self.data.test_password,
- self.data.test_tenant)
-
- @attr(type=['negative', 'gate'])
- def test_authentication_with_invalid_tenant(self):
- # User's token for an invalid tenant should not be authenticated
- self.data.setup_test_user()
- self.assertRaises(exceptions.Unauthorized, self.token_client.auth,
- self.data.test_user,
- self.data.test_password,
- 'junktenant1234')
-
- @attr(type=['negative', 'gate'])
- def test_authentication_with_invalid_username(self):
- # Non-existent user's token should not get authenticated
- self.data.setup_test_user()
- self.assertRaises(exceptions.Unauthorized, self.token_client.auth,
- 'junkuser123', self.data.test_password,
- self.data.test_tenant)
-
- @attr(type=['negative', 'gate'])
- def test_authentication_with_invalid_password(self):
- # User's token with invalid password should not be authenticated
- self.data.setup_test_user()
- self.assertRaises(exceptions.Unauthorized, self.token_client.auth,
- self.data.test_user, 'junkpass1234',
- self.data.test_tenant)
-
@attr(type='gate')
def test_authentication_request_without_token(self):
# Request for token authentication with a valid token in header
@@ -239,21 +138,6 @@
Contains(self.data.test_user),
"Could not find %s" % self.data.test_user)
- @attr(type=['negative', 'gate'])
- def test_get_users_by_unauthorized_user(self):
- # Non-administrator user should not be authorized to get user list
- self.data.setup_test_user()
- self.assertRaises(exceptions.Unauthorized,
- self.non_admin_client.get_users)
-
- @attr(type=['negative', 'gate'])
- def test_get_users_request_without_token(self):
- # Request to get list of users without a valid token should fail
- token = self.client.get_auth()
- self.client.delete_token(token)
- self.assertRaises(exceptions.Unauthorized, self.client.get_users)
- self.client.clear_auth()
-
@attr(type='gate')
def test_list_users_for_tenant(self):
# Return a list of all users for a tenant
@@ -326,21 +210,6 @@
"Failed to find user %s in fetched list" %
', '.join(m_user for m_user in missing_users))
- @attr(type=['negative', 'gate'])
- def test_list_users_with_invalid_tenant(self):
- # Should not be able to return a list of all
- # users for a non-existent tenant
- # Assign invalid tenant ids
- invalid_id = list()
- invalid_id.append(rand_name('999'))
- invalid_id.append('alpha')
- invalid_id.append(rand_name("dddd@#%%^$"))
- invalid_id.append('!@#()$%^&*?<>{}[]')
- # List the users with invalid tenant id
- for invalid in invalid_id:
- self.assertRaises(exceptions.NotFound,
- self.client.list_users_for_tenant, invalid)
-
class UsersTestXML(UsersTestJSON):
_interface = 'xml'
diff --git a/tempest/api/identity/admin/test_users_negative.py b/tempest/api/identity/admin/test_users_negative.py
new file mode 100644
index 0000000..7cb9aef
--- /dev/null
+++ b/tempest/api/identity/admin/test_users_negative.py
@@ -0,0 +1,236 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 OpenStack Foundation
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.api.identity import base
+from tempest.common.utils.data_utils import rand_name
+from tempest import exceptions
+from tempest.test import attr
+import uuid
+
+
+class UsersNegativeTestJSON(base.BaseIdentityAdminTest):
+ _interface = 'json'
+
+ @classmethod
+ def setUpClass(cls):
+ super(UsersNegativeTestJSON, cls).setUpClass()
+ cls.alt_user = rand_name('test_user_')
+ cls.alt_password = rand_name('pass_')
+ cls.alt_email = cls.alt_user + '@testmail.tm'
+ cls.alt_tenant = rand_name('test_tenant_')
+ cls.alt_description = rand_name('desc_')
+
+ @attr(type=['negative', 'gate'])
+ def test_create_user_by_unauthorized_user(self):
+ # Non-administrator should not be authorized to create a user
+ self.data.setup_test_tenant()
+ self.assertRaises(exceptions.Unauthorized,
+ self.non_admin_client.create_user, self.alt_user,
+ self.alt_password, self.data.tenant['id'],
+ self.alt_email)
+
+ @attr(type=['negative', 'gate'])
+ def test_create_user_with_empty_name(self):
+ # User with an empty name should not be created
+ self.data.setup_test_tenant()
+ self.assertRaises(exceptions.BadRequest, self.client.create_user, '',
+ self.alt_password, self.data.tenant['id'],
+ self.alt_email)
+
+ @attr(type=['negative', 'gate'])
+ def test_create_user_with_name_length_over_255(self):
+ # Length of user name filed should be restricted to 255 characters
+ self.data.setup_test_tenant()
+ self.assertRaises(exceptions.BadRequest, self.client.create_user,
+ 'a' * 256, self.alt_password,
+ self.data.tenant['id'], self.alt_email)
+
+ @attr(type=['negative', 'gate'])
+ def test_create_user_with_duplicate_name(self):
+ # Duplicate user should not be created
+ self.data.setup_test_user()
+ self.assertRaises(exceptions.Duplicate, self.client.create_user,
+ self.data.test_user, self.data.test_password,
+ self.data.tenant['id'], self.data.test_email)
+
+ @attr(type=['negative', 'gate'])
+ def test_create_user_for_non_existant_tenant(self):
+ # Attempt to create a user in a non-existent tenant should fail
+ self.assertRaises(exceptions.NotFound, self.client.create_user,
+ self.alt_user, self.alt_password, '49ffgg99999',
+ self.alt_email)
+
+ @attr(type=['negative', 'gate'])
+ def test_create_user_request_without_a_token(self):
+ # Request to create a user without a valid token should fail
+ self.data.setup_test_tenant()
+ # Get the token of the current client
+ token = self.client.get_auth()
+ # Delete the token from database
+ self.client.delete_token(token)
+ self.assertRaises(exceptions.Unauthorized, self.client.create_user,
+ self.alt_user, self.alt_password,
+ self.data.tenant['id'], self.alt_email)
+
+ # Unset the token to allow further tests to generate a new token
+ self.client.clear_auth()
+
+ @attr(type=['negative', 'gate'])
+ def test_create_user_with_enabled_non_bool(self):
+ # Attempt to create a user with valid enabled para should fail
+ self.data.setup_test_tenant()
+ name = rand_name('test_user_')
+ self.assertRaises(exceptions.BadRequest, self.client.create_user,
+ name, self.alt_password,
+ self.data.tenant['id'],
+ self.alt_email, enabled=3)
+
+ @attr(type=['negative', 'gate'])
+ def test_update_user_for_non_existant_user(self):
+ # Attempt to update a user non-existent user should fail
+ user_name = rand_name('user-')
+ non_existent_id = str(uuid.uuid4())
+ self.assertRaises(exceptions.NotFound, self.client.update_user,
+ non_existent_id, name=user_name)
+
+ @attr(type=['negative', 'gate'])
+ def test_update_user_request_without_a_token(self):
+ # Request to update a user without a valid token should fail
+
+ # Get the token of the current client
+ token = self.client.get_auth()
+ # Delete the token from database
+ self.client.delete_token(token)
+ self.assertRaises(exceptions.Unauthorized, self.client.update_user,
+ self.alt_user)
+
+ # Unset the token to allow further tests to generate a new token
+ self.client.clear_auth()
+
+ @attr(type=['negative', 'gate'])
+ def test_update_user_by_unauthorized_user(self):
+ # Non-administrator should not be authorized to update user
+ self.data.setup_test_tenant()
+ self.assertRaises(exceptions.Unauthorized,
+ self.non_admin_client.update_user, self.alt_user)
+
+ @attr(type=['negative', 'gate'])
+ def test_delete_users_by_unauthorized_user(self):
+ # Non-administrator user should not be authorized to delete a user
+ self.data.setup_test_user()
+ self.assertRaises(exceptions.Unauthorized,
+ self.non_admin_client.delete_user,
+ self.data.user['id'])
+
+ @attr(type=['negative', 'gate'])
+ def test_delete_non_existant_user(self):
+ # Attempt to delete a non-existent user should fail
+ self.assertRaises(exceptions.NotFound, self.client.delete_user,
+ 'junk12345123')
+
+ @attr(type=['negative', 'gate'])
+ def test_delete_user_request_without_a_token(self):
+ # Request to delete a user without a valid token should fail
+
+ # Get the token of the current client
+ token = self.client.get_auth()
+ # Delete the token from database
+ self.client.delete_token(token)
+ self.assertRaises(exceptions.Unauthorized, self.client.delete_user,
+ self.alt_user)
+
+ # Unset the token to allow further tests to generate a new token
+ self.client.clear_auth()
+
+ @attr(type=['negative', 'gate'])
+ def test_authentication_for_disabled_user(self):
+ # Disabled user's token should not get authenticated
+ self.data.setup_test_user()
+ self.disable_user(self.data.test_user)
+ self.assertRaises(exceptions.Unauthorized, self.token_client.auth,
+ self.data.test_user,
+ self.data.test_password,
+ self.data.test_tenant)
+
+ @attr(type=['negative', 'gate'])
+ def test_authentication_when_tenant_is_disabled(self):
+ # User's token for a disabled tenant should not be authenticated
+ self.data.setup_test_user()
+ self.disable_tenant(self.data.test_tenant)
+ self.assertRaises(exceptions.Unauthorized, self.token_client.auth,
+ self.data.test_user,
+ self.data.test_password,
+ self.data.test_tenant)
+
+ @attr(type=['negative', 'gate'])
+ def test_authentication_with_invalid_tenant(self):
+ # User's token for an invalid tenant should not be authenticated
+ self.data.setup_test_user()
+ self.assertRaises(exceptions.Unauthorized, self.token_client.auth,
+ self.data.test_user,
+ self.data.test_password,
+ 'junktenant1234')
+
+ @attr(type=['negative', 'gate'])
+ def test_authentication_with_invalid_username(self):
+ # Non-existent user's token should not get authenticated
+ self.data.setup_test_user()
+ self.assertRaises(exceptions.Unauthorized, self.token_client.auth,
+ 'junkuser123', self.data.test_password,
+ self.data.test_tenant)
+
+ @attr(type=['negative', 'gate'])
+ def test_authentication_with_invalid_password(self):
+ # User's token with invalid password should not be authenticated
+ self.data.setup_test_user()
+ self.assertRaises(exceptions.Unauthorized, self.token_client.auth,
+ self.data.test_user, 'junkpass1234',
+ self.data.test_tenant)
+
+ @attr(type=['negative', 'gate'])
+ def test_get_users_by_unauthorized_user(self):
+ # Non-administrator user should not be authorized to get user list
+ self.data.setup_test_user()
+ self.assertRaises(exceptions.Unauthorized,
+ self.non_admin_client.get_users)
+
+ @attr(type=['negative', 'gate'])
+ def test_get_users_request_without_token(self):
+ # Request to get list of users without a valid token should fail
+ token = self.client.get_auth()
+ self.client.delete_token(token)
+ self.assertRaises(exceptions.Unauthorized, self.client.get_users)
+ self.client.clear_auth()
+
+ @attr(type=['negative', 'gate'])
+ def test_list_users_with_invalid_tenant(self):
+ # Should not be able to return a list of all
+ # users for a non-existent tenant
+ # Assign invalid tenant ids
+ invalid_id = list()
+ invalid_id.append(rand_name('999'))
+ invalid_id.append('alpha')
+ invalid_id.append(rand_name("dddd@#%%^$"))
+ invalid_id.append('!@#()$%^&*?<>{}[]')
+ # List the users with invalid tenant id
+ for invalid in invalid_id:
+ self.assertRaises(exceptions.NotFound,
+ self.client.list_users_for_tenant, invalid)
+
+
+class UsersNegativeTestXML(UsersNegativeTestJSON):
+ _interface = 'xml'
diff --git a/tempest/api/volume/test_volumes_actions.py b/tempest/api/volume/test_volumes_actions.py
index 19e3fc6..09131e2 100644
--- a/tempest/api/volume/test_volumes_actions.py
+++ b/tempest/api/volume/test_volumes_actions.py
@@ -32,8 +32,8 @@
cls.image_client = cls.os.image_client
# Create a test shared instance and volume for attach/detach tests
- srv_name = rand_name('Instance-')
- vol_name = rand_name('Volume-')
+ srv_name = rand_name(cls.__name__ + '-Instance-')
+ vol_name = rand_name(cls.__name__ + '-Volume-')
resp, cls.server = cls.servers_client.create_server(srv_name,
cls.image_ref,
cls.flavor_ref)
diff --git a/tempest/api/volume/test_volumes_list.py b/tempest/api/volume/test_volumes_list.py
index 32eecfb..2aaa71d 100644
--- a/tempest/api/volume/test_volumes_list.py
+++ b/tempest/api/volume/test_volumes_list.py
@@ -1,6 +1,7 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2012 OpenStack Foundation
+# Copyright 2013 IBM Corp.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
@@ -16,6 +17,7 @@
# under the License.
from tempest.api.volume import base
+from tempest.common.utils.data_utils import rand_int_id
from tempest.common.utils.data_utils import rand_name
from tempest.openstack.common import log as logging
from tempest.test import attr
@@ -103,6 +105,66 @@
self.assertEqual(200, resp.status)
self.assertVolumesIn(fetched_list, self.volume_list)
+ @attr(type='gate')
+ def test_volume_list_by_name(self):
+ volume = self.volume_list[rand_int_id(0, 2)]
+ params = {'display_name': volume['display_name']}
+ resp, fetched_vol = self.client.list_volumes(params)
+ self.assertEqual(200, resp.status)
+ self.assertEqual(1, len(fetched_vol), str(fetched_vol))
+ self.assertEqual(fetched_vol[0]['display_name'],
+ volume['display_name'])
+
+ @attr(type='gate')
+ def test_volume_list_details_by_name(self):
+ volume = self.volume_list[rand_int_id(0, 2)]
+ params = {'display_name': volume['display_name']}
+ resp, fetched_vol = self.client.list_volumes_with_detail(params)
+ self.assertEqual(200, resp.status)
+ self.assertEqual(1, len(fetched_vol), str(fetched_vol))
+ self.assertEqual(fetched_vol[0]['display_name'],
+ volume['display_name'])
+
+ @attr(type='gate')
+ def test_volumes_list_by_status(self):
+ params = {'status': 'available'}
+ resp, fetched_list = self.client.list_volumes(params)
+ self.assertEqual(200, resp.status)
+ for volume in fetched_list:
+ self.assertEqual('available', volume['status'])
+ self.assertVolumesIn(fetched_list, self.volume_list)
+
+ @attr(type='gate')
+ def test_volumes_list_details_by_status(self):
+ params = {'status': 'available'}
+ resp, fetched_list = self.client.list_volumes_with_detail(params)
+ self.assertEqual(200, resp.status)
+ for volume in fetched_list:
+ self.assertEqual('available', volume['status'])
+ self.assertVolumesIn(fetched_list, self.volume_list)
+
+ @attr(type='gate')
+ def test_volumes_list_by_availability_zone(self):
+ volume = self.volume_list[rand_int_id(0, 2)]
+ zone = volume['availability_zone']
+ params = {'availability_zone': zone}
+ resp, fetched_list = self.client.list_volumes(params)
+ self.assertEqual(200, resp.status)
+ for volume in fetched_list:
+ self.assertEqual(zone, volume['availability_zone'])
+ self.assertVolumesIn(fetched_list, self.volume_list)
+
+ @attr(type='gate')
+ def test_volumes_list_details_by_availability_zone(self):
+ volume = self.volume_list[rand_int_id(0, 2)]
+ zone = volume['availability_zone']
+ params = {'availability_zone': zone}
+ resp, fetched_list = self.client.list_volumes_with_detail(params)
+ self.assertEqual(200, resp.status)
+ for volume in fetched_list:
+ self.assertEqual(zone, volume['availability_zone'])
+ self.assertVolumesIn(fetched_list, self.volume_list)
+
class VolumeListTestXML(VolumesListTest):
_interface = 'xml'
diff --git a/tempest/api/volume/test_volumes_negative.py b/tempest/api/volume/test_volumes_negative.py
index a48cea2..3e2b6ad 100644
--- a/tempest/api/volume/test_volumes_negative.py
+++ b/tempest/api/volume/test_volumes_negative.py
@@ -38,19 +38,19 @@
cls.client.wait_for_volume_status(cls.volume['id'], 'available')
cls.mountpoint = "/dev/vdc"
- @attr(type='gate')
+ @attr(type=['negative', 'gate'])
def test_volume_get_nonexistant_volume_id(self):
# Should not be able to get a non-existant volume
self.assertRaises(exceptions.NotFound, self.client.get_volume,
str(uuid.uuid4()))
- @attr(type='gate')
+ @attr(type=['negative', 'gate'])
def test_volume_delete_nonexistant_volume_id(self):
# Should not be able to delete a non-existant Volume
self.assertRaises(exceptions.NotFound, self.client.delete_volume,
str(uuid.uuid4()))
- @attr(type='gate')
+ @attr(type=['negative', 'gate'])
def test_create_volume_with_invalid_size(self):
# Should not be able to create volume with invalid size
# in request
@@ -59,7 +59,7 @@
self.assertRaises(exceptions.BadRequest, self.client.create_volume,
size='#$%', display_name=v_name, metadata=metadata)
- @attr(type='gate')
+ @attr(type=['negative', 'gate'])
def test_create_volume_with_out_passing_size(self):
# Should not be able to create volume without passing size
# in request
@@ -68,7 +68,7 @@
self.assertRaises(exceptions.BadRequest, self.client.create_volume,
size='', display_name=v_name, metadata=metadata)
- @attr(type='gate')
+ @attr(type=['negative', 'gate'])
def test_create_volume_with_size_zero(self):
# Should not be able to create volume with size zero
v_name = rand_name('Volume-')
@@ -76,24 +76,56 @@
self.assertRaises(exceptions.BadRequest, self.client.create_volume,
size='0', display_name=v_name, metadata=metadata)
- @attr(type='gate')
+ @attr(type=['negative', 'gate'])
+ def test_create_volume_with_size_negative(self):
+ # Should not be able to create volume with size negative
+ v_name = rand_name('Volume-')
+ metadata = {'Type': 'work'}
+ self.assertRaises(exceptions.BadRequest, self.client.create_volume,
+ size='-1', display_name=v_name, metadata=metadata)
+
+ @attr(type=['negative', 'gate'])
+ def test_update_volume_with_nonexistant_volume_id(self):
+ v_name = rand_name('Volume-')
+ metadata = {'Type': 'work'}
+ self.assertRaises(exceptions.NotFound, self.client.update_volume,
+ volume_id=str(uuid.uuid4()), display_name=v_name,
+ metadata=metadata)
+
+ @attr(type=['negative', 'gate'])
+ def test_update_volume_with_invalid_volume_id(self):
+ v_name = rand_name('Volume-')
+ metadata = {'Type': 'work'}
+ self.assertRaises(exceptions.NotFound, self.client.update_volume,
+ volume_id='#$%%&^&^', display_name=v_name,
+ metadata=metadata)
+
+ @attr(type=['negative', 'gate'])
+ def test_update_volume_with_empty_volume_id(self):
+ v_name = rand_name('Volume-')
+ metadata = {'Type': 'work'}
+ self.assertRaises(exceptions.NotFound, self.client.update_volume,
+ volume_id='', display_name=v_name,
+ metadata=metadata)
+
+ @attr(type=['negative', 'gate'])
def test_get_invalid_volume_id(self):
# Should not be able to get volume with invalid id
self.assertRaises(exceptions.NotFound, self.client.get_volume,
'#$%%&^&^')
- @attr(type='gate')
+ @attr(type=['negative', 'gate'])
def test_get_volume_without_passing_volume_id(self):
# Should not be able to get volume when empty ID is passed
self.assertRaises(exceptions.NotFound, self.client.get_volume, '')
- @attr(type='gate')
+ @attr(type=['negative', 'gate'])
def test_delete_invalid_volume_id(self):
# Should not be able to delete volume when invalid ID is passed
self.assertRaises(exceptions.NotFound, self.client.delete_volume,
'!@#$%^&*()')
- @attr(type='gate')
+ @attr(type=['negative', 'gate'])
def test_delete_volume_without_passing_volume_id(self):
# Should not be able to delete volume when empty ID is passed
self.assertRaises(exceptions.NotFound, self.client.delete_volume, '')
diff --git a/tempest/cli/simple_read_only/test_cinder.py b/tempest/cli/simple_read_only/test_cinder.py
index 3ff997a..25157a4 100644
--- a/tempest/cli/simple_read_only/test_cinder.py
+++ b/tempest/cli/simple_read_only/test_cinder.py
@@ -84,6 +84,24 @@
roles = self.parser.listing(self.cinder('list-extensions'))
self.assertTableStruct(roles, ['Name', 'Summary', 'Alias', 'Updated'])
+ def test_cinder_credentials(self):
+ self.cinder('credentials')
+
+ def test_cinder_availability_zone_list(self):
+ self.cinder('availability-zone-list')
+
+ def test_cinder_endpoints(self):
+ self.cinder('endpoints')
+
+ def test_cinder_service_list(self):
+ self.cinder('service-list')
+
+ def test_cinder_transfer_list(self):
+ self.cinder('transfer-list')
+
+ def test_cinder_bash_completion(self):
+ self.cinder('bash-completion')
+
def test_admin_help(self):
help_text = self.cinder('help')
lines = help_text.split('\n')
diff --git a/tempest/cli/simple_read_only/test_neutron.py b/tempest/cli/simple_read_only/test_neutron.py
index ae3a1a7..3c266be 100644
--- a/tempest/cli/simple_read_only/test_neutron.py
+++ b/tempest/cli/simple_read_only/test_neutron.py
@@ -67,6 +67,12 @@
def test_neutron_floatingip_list(self):
self.neutron('floatingip-list')
+ def test_neutron_meter_label_list(self):
+ self.neutron('meter-label-list')
+
+ def test_neutron_meter_label_rule_list(self):
+ self.neutron('meter-label-rule-list')
+
def test_neutron_net_external_list(self):
self.neutron('net-external-list')
diff --git a/tempest/cli/simple_read_only/test_compute.py b/tempest/cli/simple_read_only/test_nova.py
similarity index 97%
rename from tempest/cli/simple_read_only/test_compute.py
rename to tempest/cli/simple_read_only/test_nova.py
index 9b358e6..94415f0 100644
--- a/tempest/cli/simple_read_only/test_compute.py
+++ b/tempest/cli/simple_read_only/test_nova.py
@@ -167,6 +167,10 @@
def test_admin_net_list(self):
self.nova('net-list')
+ def test_agent_list(self):
+ self.nova('agent-list')
+ self.nova('agent-list', flags='--debug')
+
# Optional arguments:
def test_admin_version(self):
diff --git a/tempest/cli/simple_read_only/test_compute_manage.py b/tempest/cli/simple_read_only/test_nova_manage.py
similarity index 92%
rename from tempest/cli/simple_read_only/test_compute_manage.py
rename to tempest/cli/simple_read_only/test_nova_manage.py
index 523c65f..9a33556 100644
--- a/tempest/cli/simple_read_only/test_compute_manage.py
+++ b/tempest/cli/simple_read_only/test_nova_manage.py
@@ -81,3 +81,11 @@
def test_db_version(self):
self.assertNotEqual("", self.nova_manage('db version'))
+
+ def test_cell_list(self):
+ # make sure command doesn't error out
+ self.nova_manage('cell list')
+
+ def test_host_list(self):
+ # make sure command doesn't error out
+ self.nova_manage('host list')
diff --git a/tempest/common/tempest_fixtures.py b/tempest/common/tempest_fixtures.py
index 081b271..ebc9ad3 100644
--- a/tempest/common/tempest_fixtures.py
+++ b/tempest/common/tempest_fixtures.py
@@ -15,16 +15,9 @@
# License for the specific language governing permissions and limitations
# under the License.
-import fixtures
-
from tempest.openstack.common import lockutils
-class LockFixture(fixtures.Fixture):
+class LockFixture(lockutils.LockFixture):
def __init__(self, name):
- self.mgr = lockutils.lock(name, 'tempest-', True)
-
- def setUp(self):
- super(LockFixture, self).setUp()
- self.addCleanup(self.mgr.__exit__, None, None, None)
- self.mgr.__enter__()
+ super(LockFixture, self).__init__(name, 'tempest-')
diff --git a/tempest/config.py b/tempest/config.py
index af78381..db923e9 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -27,6 +27,12 @@
from tempest.openstack.common import log as logging
+def register_opt_group(conf, opt_group, options):
+ conf.register_group(opt_group)
+ for opt in options:
+ conf.register_opt(opt, group=opt_group.name)
+
+
identity_group = cfg.OptGroup(name='identity',
title="Keystone Configuration Options")
@@ -87,13 +93,6 @@
secret=True),
]
-
-def register_identity_opts(conf):
- conf.register_group(identity_group)
- for opt in IdentityGroup:
- conf.register_opt(opt, group='identity')
-
-
compute_group = cfg.OptGroup(name='compute',
title='Compute Service Options')
@@ -225,12 +224,6 @@
"an instance")
]
-
-def register_compute_opts(conf):
- conf.register_group(compute_group)
- for opt in ComputeGroup:
- conf.register_opt(opt, group='compute')
-
compute_admin_group = cfg.OptGroup(name='compute-admin',
title="Compute Admin Options")
@@ -248,12 +241,6 @@
secret=True),
]
-
-def register_compute_admin_opts(conf):
- conf.register_group(compute_admin_group)
- for opt in ComputeAdminGroup:
- conf.register_opt(opt, group='compute-admin')
-
image_group = cfg.OptGroup(name='image',
title="Image Service Options")
@@ -277,12 +264,6 @@
]
-def register_image_opts(conf):
- conf.register_group(image_group)
- for opt in ImageGroup:
- conf.register_opt(opt, group='image')
-
-
network_group = cfg.OptGroup(name='network',
title='Network Service Options')
@@ -316,12 +297,6 @@
"connectivity"),
]
-
-def register_network_opts(conf):
- conf.register_group(network_group)
- for opt in NetworkGroup:
- conf.register_opt(opt, group='network')
-
volume_group = cfg.OptGroup(name='volume',
title='Block Storage Options')
@@ -363,12 +338,6 @@
]
-def register_volume_opts(conf):
- conf.register_group(volume_group)
- for opt in VolumeGroup:
- conf.register_opt(opt, group='volume')
-
-
object_storage_group = cfg.OptGroup(name='object-storage',
title='Object Storage Service Options')
@@ -404,12 +373,6 @@
]
-def register_object_storage_opts(conf):
- conf.register_group(object_storage_group)
- for opt in ObjectStoreGroup:
- conf.register_opt(opt, group='object-storage')
-
-
orchestration_group = cfg.OptGroup(name='orchestration',
title='Orchestration Service Options')
@@ -447,17 +410,11 @@
default=None,
help="Name of existing keypair to launch servers with."),
cfg.IntOpt('max_template_size',
- default=10240,
+ default=32768,
help="Value must match heat configuration of the same name."),
]
-def register_orchestration_opts(conf):
- conf.register_group(orchestration_group)
- for opt in OrchestrationGroup:
- conf.register_opt(opt, group='orchestration')
-
-
dashboard_group = cfg.OptGroup(name="dashboard",
title="Dashboard options")
@@ -471,12 +428,6 @@
]
-def register_dashboard_opts(conf):
- conf.register_group(dashboard_group)
- for opt in DashboardGroup:
- conf.register_opt(opt, group='dashboard')
-
-
boto_group = cfg.OptGroup(name='boto',
title='EC2/S3 options')
BotoGroup = [
@@ -523,12 +474,6 @@
help="Status Change Test Interval"),
]
-
-def register_boto_opts(conf):
- conf.register_group(boto_group)
- for opt in BotoGroup:
- conf.register_opt(opt, group='boto')
-
stress_group = cfg.OptGroup(name='stress', title='Stress Test Options')
StressGroup = [
@@ -563,12 +508,6 @@
]
-def register_stress_opts(conf):
- conf.register_group(stress_group)
- for opt in StressGroup:
- conf.register_opt(opt, group='stress')
-
-
scenario_group = cfg.OptGroup(name='scenario', title='Scenario Test Options')
ScenarioGroup = [
@@ -596,12 +535,6 @@
]
-def register_scenario_opts(conf):
- conf.register_group(scenario_group)
- for opt in ScenarioGroup:
- conf.register_opt(opt, group='scenario')
-
-
service_available_group = cfg.OptGroup(name="service_available",
title="Available OpenStack Services")
@@ -629,12 +562,6 @@
help="Whether or not Horizon is expected to be available"),
]
-
-def register_service_available_opts(conf):
- conf.register_group(service_available_group)
- for opt in ServiceAvailableGroup:
- conf.register_opt(opt, group='service_available')
-
debug_group = cfg.OptGroup(name="debug",
title="Debug System")
@@ -645,12 +572,6 @@
]
-def register_debug_opts(conf):
- conf.register_group(debug_group)
- for opt in DebugGroup:
- conf.register_opt(opt, group='debug')
-
-
@singleton
class TempestConfig:
"""Provides OpenStack configuration information."""
@@ -689,20 +610,21 @@
LOG = logging.getLogger('tempest')
LOG.info("Using tempest config file %s" % path)
- register_compute_opts(cfg.CONF)
- register_identity_opts(cfg.CONF)
- register_image_opts(cfg.CONF)
- register_network_opts(cfg.CONF)
- register_volume_opts(cfg.CONF)
- register_object_storage_opts(cfg.CONF)
- register_orchestration_opts(cfg.CONF)
- register_dashboard_opts(cfg.CONF)
- register_boto_opts(cfg.CONF)
- register_compute_admin_opts(cfg.CONF)
- register_stress_opts(cfg.CONF)
- register_scenario_opts(cfg.CONF)
- register_service_available_opts(cfg.CONF)
- register_debug_opts(cfg.CONF)
+ register_opt_group(cfg.CONF, compute_group, ComputeGroup)
+ register_opt_group(cfg.CONF, identity_group, IdentityGroup)
+ register_opt_group(cfg.CONF, image_group, ImageGroup)
+ register_opt_group(cfg.CONF, network_group, NetworkGroup)
+ register_opt_group(cfg.CONF, volume_group, VolumeGroup)
+ register_opt_group(cfg.CONF, object_storage_group, ObjectStoreGroup)
+ register_opt_group(cfg.CONF, orchestration_group, OrchestrationGroup)
+ register_opt_group(cfg.CONF, dashboard_group, DashboardGroup)
+ register_opt_group(cfg.CONF, boto_group, BotoGroup)
+ register_opt_group(cfg.CONF, compute_admin_group, ComputeAdminGroup)
+ register_opt_group(cfg.CONF, stress_group, StressGroup)
+ register_opt_group(cfg.CONF, scenario_group, ScenarioGroup)
+ register_opt_group(cfg.CONF, service_available_group,
+ ServiceAvailableGroup)
+ register_opt_group(cfg.CONF, debug_group, DebugGroup)
self.compute = cfg.CONF.compute
self.identity = cfg.CONF.identity
self.images = cfg.CONF.image
diff --git a/tempest/openstack/common/lockutils.py b/tempest/openstack/common/lockutils.py
index 0abd1a7..a55fd94 100644
--- a/tempest/openstack/common/lockutils.py
+++ b/tempest/openstack/common/lockutils.py
@@ -24,6 +24,7 @@
import time
import weakref
+import fixtures
from oslo.config import cfg
from tempest.openstack.common import fileutils
@@ -275,3 +276,36 @@
"""
return functools.partial(synchronized, lock_file_prefix=lock_file_prefix)
+
+
+class LockFixture(fixtures.Fixture):
+ """External locking fixture.
+
+ This fixture is basically an alternative to the synchronized decorator with
+ the external flag so that tearDowns and addCleanups will be included in
+ the lock context for locking between tests. The fixture is recommended to
+ be the first line in a test method, like so::
+
+ def test_method(self):
+ self.useFixture(LockFixture)
+ ...
+
+ or the first line in setUp if all the test methods in the class are
+ required to be serialized. Something like::
+
+ class TestCase(testtools.testcase):
+ def setUp(self):
+ self.useFixture(LockFixture)
+ super(TestCase, self).setUp()
+ ...
+
+ This is because addCleanups are put on a LIFO queue that gets run after the
+ test method exits. (either by completing or raising an exception)
+ """
+ def __init__(self, name, lock_file_prefix=None):
+ self.mgr = lock(name, lock_file_prefix, True)
+
+ def setUp(self):
+ super(LockFixture, self).setUp()
+ self.addCleanup(self.mgr.__exit__, None, None, None)
+ self.mgr.__enter__()
diff --git a/tempest/scenario/manager.py b/tempest/scenario/manager.py
index c4b98d5..8ccc899 100644
--- a/tempest/scenario/manager.py
+++ b/tempest/scenario/manager.py
@@ -280,16 +280,23 @@
cls.os_resources.remove(thing)
del cls.resource_keys[key]
- def status_timeout(self, things, thing_id, expected_status):
+ def status_timeout(self, things, thing_id, expected_status,
+ error_status='ERROR',
+ not_found_exception=nova_exceptions.NotFound):
"""
Given a thing and an expected status, do a loop, sleeping
for a configurable amount of time, checking for the
expected status to show. At any time, if the returned
status of the thing is ERROR, fail out.
"""
- self._status_timeout(things, thing_id, expected_status=expected_status)
+ self._status_timeout(things, thing_id,
+ expected_status=expected_status,
+ error_status=error_status,
+ not_found_exception=not_found_exception)
- def delete_timeout(self, things, thing_id):
+ def delete_timeout(self, things, thing_id,
+ error_status='ERROR',
+ not_found_exception=nova_exceptions.NotFound):
"""
Given a thing, do a loop, sleeping
for a configurable amount of time, checking for the
@@ -298,13 +305,17 @@
"""
self._status_timeout(things,
thing_id,
- allow_notfound=True)
+ allow_notfound=True,
+ error_status=error_status,
+ not_found_exception=not_found_exception)
def _status_timeout(self,
things,
thing_id,
expected_status=None,
- allow_notfound=False):
+ allow_notfound=False,
+ error_status='ERROR',
+ not_found_exception=nova_exceptions.NotFound):
log_status = expected_status if expected_status else ''
if allow_notfound:
@@ -316,16 +327,16 @@
# for the singular resource to retrieve.
try:
thing = things.get(thing_id)
- except nova_exceptions.NotFound:
+ except not_found_exception:
if allow_notfound:
return True
else:
raise
new_status = thing.status
- if new_status == 'ERROR':
+ if new_status == error_status:
message = "%s failed to get to expected status. \
- In ERROR state." % (thing)
+ In %s state." % (thing, new_status)
raise exceptions.BuildErrorException(message)
elif new_status == expected_status and expected_status is not None:
return True # All good.
diff --git a/tempest/scenario/test_large_ops.py b/tempest/scenario/test_large_ops.py
index 33b7adc..22c543b 100644
--- a/tempest/scenario/test_large_ops.py
+++ b/tempest/scenario/test_large_ops.py
@@ -24,7 +24,7 @@
LOG = logging.getLogger(__name__)
-class TestLargeOpsScenario(manager.OfficialClientTest):
+class TestLargeOpsScenario(manager.NetworkScenarioTest):
"""
Test large operations.
@@ -82,19 +82,19 @@
properties=properties)
def nova_boot(self):
- def delete(servers):
- [x.delete() for x in servers]
-
name = rand_name('scenario-server-')
client = self.compute_client
flavor_id = self.config.compute.flavor_ref
+ secgroup = self._create_security_group()
self.servers = client.servers.create(
name=name, image=self.image,
flavor=flavor_id,
- min_count=self.config.scenario.large_ops_number)
+ min_count=self.config.scenario.large_ops_number,
+ security_groups=[secgroup.name])
# needed because of bug 1199788
self.servers = [x for x in client.servers.list() if name in x.name]
- self.addCleanup(delete, self.servers)
+ for server in self.servers:
+ self.set_resource(server.name, server)
self._wait_for_server_status('ACTIVE')
@services('compute', 'image')
diff --git a/tempest/services/compute/json/servers_client.py b/tempest/services/compute/json/servers_client.py
index 990c986..eda0ede 100644
--- a/tempest/services/compute/json/servers_client.py
+++ b/tempest/services/compute/json/servers_client.py
@@ -366,6 +366,11 @@
"""Unrescue the provided server."""
return self.action(server_id, 'unrescue', None)
+ def get_server_diagnostics(self, server_id):
+ """Get the usage data for a server."""
+ resp, body = self.get("servers/%s/diagnostics" % str(server_id))
+ return resp, json.loads(body)
+
def list_instance_actions(self, server_id):
"""List the provided server action."""
resp, body = self.get("servers/%s/os-instance-actions" %
diff --git a/tempest/services/compute/xml/servers_client.py b/tempest/services/compute/xml/servers_client.py
index c58c3ee..cb21c61 100644
--- a/tempest/services/compute/xml/servers_client.py
+++ b/tempest/services/compute/xml/servers_client.py
@@ -575,6 +575,13 @@
(server_id, volume_id), headers)
return resp, body
+ def get_server_diagnostics(self, server_id):
+ """Get the usage data for a server."""
+ resp, body = self.get("servers/%s/diagnostics" % server_id,
+ self.headers)
+ body = xml_to_json(etree.fromstring(body))
+ return resp, body
+
def list_instance_actions(self, server_id):
"""List the provided server action."""
resp, body = self.get("servers/%s/os-instance-actions" % server_id,
diff --git a/tempest/services/identity/json/identity_client.py b/tempest/services/identity/json/identity_client.py
index 18132ed..94045b8 100644
--- a/tempest/services/identity/json/identity_client.py
+++ b/tempest/services/identity/json/identity_client.py
@@ -139,7 +139,7 @@
body = json.loads(body)
return resp, body['tenant']
- def create_user(self, name, password, tenant_id, email):
+ def create_user(self, name, password, tenant_id, email, **kwargs):
"""Create a user."""
post_body = {
'name': name,
@@ -147,6 +147,8 @@
'tenantId': tenant_id,
'email': email
}
+ if kwargs.get('enabled') is not None:
+ post_body['enabled'] = kwargs.get('enabled')
post_body = json.dumps({'user': post_body})
resp, body = self.post('users', post_body, self.headers)
body = json.loads(body)
diff --git a/tempest/services/identity/xml/identity_client.py b/tempest/services/identity/xml/identity_client.py
index 9d44826..9c0a72c 100644
--- a/tempest/services/identity/xml/identity_client.py
+++ b/tempest/services/identity/xml/identity_client.py
@@ -159,7 +159,7 @@
body = self._parse_body(etree.fromstring(body))
return resp, body
- def create_user(self, name, password, tenant_id, email):
+ def create_user(self, name, password, tenant_id, email, **kwargs):
"""Create a user."""
create_user = Element("user",
xmlns=XMLNS,
@@ -167,6 +167,9 @@
password=password,
tenantId=tenant_id,
email=email)
+ if 'enabled' in kwargs:
+ create_user.add_attr('enabled', str(kwargs['enabled']).lower())
+
resp, body = self.post('users', str(Document(create_user)),
self.headers)
body = self._parse_body(etree.fromstring(body))
diff --git a/tempest/stress/driver.py b/tempest/stress/driver.py
index 55404ce..b5cab68 100644
--- a/tempest/stress/driver.py
+++ b/tempest/stress/driver.py
@@ -196,7 +196,7 @@
if not logfiles:
continue
- if _has_error_in_logs(logfiles, computes):
+ if _has_error_in_logs(logfiles, computes, stop_on_error):
had_errors = True
break
diff --git a/tempest/stress/run_stress.py b/tempest/stress/run_stress.py
index 886d94b..e5cc281 100755
--- a/tempest/stress/run_stress.py
+++ b/tempest/stress/run_stress.py
@@ -23,14 +23,20 @@
from testtools.testsuite import iterate_tests
from unittest import loader
+from tempest.openstack.common import log as logging
+
+LOG = logging.getLogger(__name__)
+
def discover_stress_tests(path="./", filter_attr=None, call_inherited=False):
"""Discovers all tempest tests and create action out of them
"""
+ LOG.info("Start test discovery")
tests = []
testloader = loader.TestLoader()
list = testloader.discover(path)
for func in (iterate_tests(list)):
+ attrs = []
try:
method_name = getattr(func, '_testMethodName')
full_name = "%s.%s.%s" % (func.__module__,
@@ -106,4 +112,8 @@
help="Name of the file with test description")
if __name__ == "__main__":
- sys.exit(main(parser.parse_args()))
+ try:
+ sys.exit(main(parser.parse_args()))
+ except Exception:
+ LOG.exception("Failure in the stress test framework")
+ sys.exit(1)
diff --git a/tools/check_logs.py b/tools/check_logs.py
index 0cc3677..2ad4f70 100755
--- a/tools/check_logs.py
+++ b/tools/check_logs.py
@@ -16,7 +16,140 @@
# License for the specific language governing permissions and limitations
# under the License.
+import argparse
+import gzip
+import os
+import re
+import StringIO
import sys
+import urllib2
+import yaml
+
+
+def process_files(file_specs, url_specs, whitelists):
+ regexp = re.compile(r"^.*(ERROR|CRITICAL).*\[.*\-.*\]")
+ had_errors = False
+ for (name, filename) in file_specs:
+ whitelist = whitelists.get(name, [])
+ with open(filename) as content:
+ if scan_content(name, content, regexp, whitelist):
+ had_errors = True
+ for (name, url) in url_specs:
+ whitelist = whitelists.get(name, [])
+ req = urllib2.Request(url)
+ req.add_header('Accept-Encoding', 'gzip')
+ page = urllib2.urlopen(req)
+ buf = StringIO.StringIO(page.read())
+ f = gzip.GzipFile(fileobj=buf)
+ if scan_content(name, f.read().splitlines(), regexp, whitelist):
+ had_errors = True
+ return had_errors
+
+
+def scan_content(name, content, regexp, whitelist):
+ had_errors = False
+ for line in content:
+ if not line.startswith("Stderr:") and regexp.match(line):
+ whitelisted = False
+ for w in whitelist:
+ pat = ".*%s.*%s.*" % (w['module'].replace('.', '\\.'),
+ w['message'])
+ if re.match(pat, line):
+ whitelisted = True
+ break
+ if not whitelisted:
+ if not had_errors:
+ print("Log File: %s" % name)
+ had_errors = True
+ print(line)
+ return had_errors
+
+
+def collect_url_logs(url):
+ page = urllib2.urlopen(url)
+ content = page.read()
+ logs = re.findall('(screen-[\w-]+\.txt\.gz)</a>', content)
+ return logs
+
+
+def main(opts):
+ if opts.directory and opts.url or not (opts.directory or opts.url):
+ print("Must provide exactly one of -d or -u")
+ exit(1)
+ print("Checking logs...")
+ WHITELIST_FILE = os.path.join(
+ os.path.abspath(os.path.dirname(os.path.dirname(__file__))),
+ "etc", "whitelist.yaml")
+
+ file_matcher = re.compile(r".*screen-([\w-]+)\.log")
+ files = []
+ if opts.directory:
+ d = opts.directory
+ for f in os.listdir(d):
+ files.append(os.path.join(d, f))
+ files_to_process = []
+ for f in files:
+ m = file_matcher.match(f)
+ if m:
+ files_to_process.append((m.group(1), f))
+
+ url_matcher = re.compile(r".*screen-([\w-]+)\.txt\.gz")
+ urls = []
+ if opts.url:
+ for logfile in collect_url_logs(opts.url):
+ urls.append("%s/%s" % (opts.url, logfile))
+ urls_to_process = []
+ for u in urls:
+ m = url_matcher.match(u)
+ if m:
+ urls_to_process.append((m.group(1), u))
+
+ whitelists = {}
+ with open(WHITELIST_FILE) as stream:
+ loaded = yaml.safe_load(stream)
+ if loaded:
+ for (name, l) in loaded.iteritems():
+ for w in l:
+ assert 'module' in w, 'no module in %s' % name
+ assert 'message' in w, 'no message in %s' % name
+ whitelists = loaded
+ if process_files(files_to_process, urls_to_process, whitelists):
+ print("Logs have errors")
+ # Return non-zero to start failing builds
+ return 0
+ else:
+ print("ok")
+ return 0
+
+usage = """
+Find non-white-listed log errors in log files from a devstack-gate run.
+Log files will be searched for ERROR or CRITICAL messages. If any
+error messages do not match any of the whitelist entries contained in
+etc/whitelist.yaml, those messages will be printed to the console and
+failure will be returned. A file directory containing logs or a url to the
+log files of an OpenStack gate job can be provided.
+
+The whitelist yaml looks like:
+
+log-name:
+ - module: "a.b.c"
+ message: "regexp"
+ - module: "a.b.c"
+ message: "regexp"
+
+repeated for each log file with a whitelist.
+"""
+
+parser = argparse.ArgumentParser(description=usage)
+parser.add_argument('-d', '--directory',
+ help="Directory containing log files")
+parser.add_argument('-u', '--url',
+ help="url containing logs from an OpenStack gate job")
if __name__ == "__main__":
- sys.exit(0)
+ try:
+ sys.exit(main(parser.parse_args()))
+ except Exception as e:
+ print("Failure in script: %s" % e)
+ # Don't fail if there is a problem with the script.
+ sys.exit(0)
diff --git a/tools/find_stack_traces.py b/tools/find_stack_traces.py
index 0ce1500..52a5a66 100755
--- a/tools/find_stack_traces.py
+++ b/tools/find_stack_traces.py
@@ -65,7 +65,9 @@
def hunt_for_stacktrace(url):
"""Return TRACE or ERROR lines out of logs."""
- page = urllib2.urlopen(url)
+ req = urllib2.Request(url)
+ req.add_header('Accept-Encoding', 'gzip')
+ page = urllib2.urlopen(req)
buf = StringIO.StringIO(page.read())
f = gzip.GzipFile(fileobj=buf)
content = f.read()
diff --git a/tools/tempest_auto_config.py b/tools/tempest_auto_config.py
new file mode 100644
index 0000000..aef6a1f
--- /dev/null
+++ b/tools/tempest_auto_config.py
@@ -0,0 +1,234 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 OpenStack Foundation
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+# Config
+import ConfigParser
+import os
+
+# Default client libs
+import keystoneclient.v2_0.client as keystone_client
+
+# Import Openstack exceptions
+import keystoneclient.exceptions as keystone_exception
+
+
+DEFAULT_CONFIG_DIR = "%s/etc" % os.path.abspath(os.path.pardir)
+DEFAULT_CONFIG_FILE = "tempest.conf"
+DEFAULT_CONFIG_SAMPLE = "tempest.conf.sample"
+
+# Environment variables override defaults
+TEMPEST_CONFIG_DIR = os.environ.get('TEMPEST_CONFIG_DIR') or DEFAULT_CONFIG_DIR
+TEMPEST_CONFIG = os.environ.get('TEMPEST_CONFIG') or "%s/%s" % \
+ (TEMPEST_CONFIG_DIR, DEFAULT_CONFIG_FILE)
+TEMPEST_CONFIG_SAMPLE = os.environ.get('TEMPEST_CONFIG_SAMPLE') or "%s/%s" % \
+ (TEMPEST_CONFIG_DIR, DEFAULT_CONFIG_SAMPLE)
+
+# Admin credentials
+OS_USERNAME = os.environ.get('OS_USERNAME')
+OS_PASSWORD = os.environ.get('OS_PASSWORD')
+OS_TENANT_NAME = os.environ.get('OS_TENANT_NAME')
+OS_AUTH_URL = os.environ.get('OS_AUTH_URL')
+
+# Image references
+IMAGE_ID = os.environ.get('IMAGE_ID')
+IMAGE_ID_ALT = os.environ.get('IMAGE_ID_ALT')
+
+
+class ClientManager(object):
+ """
+ Manager that provides access to the official python clients for
+ calling various OpenStack APIs.
+ """
+ def __init__(self):
+ self.identity_client = None
+ self.image_client = None
+ self.network_client = None
+ self.compute_client = None
+ self.volume_client = None
+
+ def get_identity_client(self, **kwargs):
+ """
+ Returns the openstack identity python client
+ :param username: a string representing the username
+ :param password: a string representing the user's password
+ :param tenant_name: a string representing the tenant name of the user
+ :param auth_url: a string representing the auth url of the identity
+ :param insecure: True if we wish to disable ssl certificate validation,
+ False otherwise
+ :returns an instance of openstack identity python client
+ """
+ if not self.identity_client:
+ self.identity_client = keystone_client.Client(**kwargs)
+
+ return self.identity_client
+
+
+def getTempestConfigSample():
+ """
+ Gets the tempest configuration file as a ConfigParser object
+ :return: the tempest configuration file
+ """
+ # get the sample config file from the sample
+ config_sample = ConfigParser.ConfigParser()
+ config_sample.readfp(open(TEMPEST_CONFIG_SAMPLE))
+
+ return config_sample
+
+
+def update_config_admin_credentials(config, config_section):
+ """
+ Updates the tempest config with the admin credentials
+ :param config: an object representing the tempest config file
+ :param config_section: the section name where the admin credentials are
+ """
+ # Check if credentials are present
+ if not (OS_AUTH_URL and
+ OS_USERNAME and
+ OS_PASSWORD and
+ OS_TENANT_NAME):
+ raise Exception("Admin environment variables not found.")
+
+ # TODO(tkammer): Add support for uri_v3
+ config_identity_params = {'uri': OS_AUTH_URL,
+ 'admin_username': OS_USERNAME,
+ 'admin_password': OS_PASSWORD,
+ 'admin_tenant_name': OS_TENANT_NAME}
+
+ update_config_section_with_params(config,
+ config_section,
+ config_identity_params)
+
+
+def update_config_section_with_params(config, section, params):
+ """
+ Updates a given config object with given params
+ :param config: the object representing the config file of tempest
+ :param section: the section we would like to update
+ :param params: the parameters we wish to update for that section
+ """
+ for option, value in params.items():
+ config.set(section, option, value)
+
+
+def get_identity_client_kwargs(config, section_name):
+ """
+ Get the required arguments for the identity python client
+ :param config: the tempest configuration file
+ :param section_name: the section name in the configuration where the
+ arguments can be found
+ :return: a dictionary representing the needed arguments for the identity
+ client
+ """
+ username = config.get(section_name, 'admin_username')
+ password = config.get(section_name, 'admin_password')
+ tenant_name = config.get(section_name, 'admin_tenant_name')
+ auth_url = config.get(section_name, 'uri')
+ dscv = config.get(section_name, 'disable_ssl_certificate_validation')
+ kwargs = {'username': username,
+ 'password': password,
+ 'tenant_name': tenant_name,
+ 'auth_url': auth_url,
+ 'insecure': dscv}
+
+ return kwargs
+
+
+def create_user_with_tenant(identity_client, username, password, tenant_name):
+ """
+ Creates a user using a given identity client
+ :param identity_client: openstack identity python client
+ :param username: a string representing the username
+ :param password: a string representing the user's password
+ :param tenant_name: a string representing the tenant name of the user
+ """
+ # Try to create the necessary tenant
+ tenant_id = None
+ try:
+ tenant_description = "Tenant for Tempest %s user" % username
+ tenant = identity_client.tenants.create(tenant_name,
+ tenant_description)
+ tenant_id = tenant.id
+ except keystone_exception.Conflict:
+
+ # if already exist, use existing tenant
+ tenant_list = identity_client.tenants.list()
+ for tenant in tenant_list:
+ if tenant.name == tenant_name:
+ tenant_id = tenant.id
+
+ # Try to create the user
+ try:
+ email = "%s@test.com" % username
+ identity_client.users.create(name=username,
+ password=password,
+ email=email,
+ tenant_id=tenant_id)
+ except keystone_exception.Conflict:
+
+ # if already exist, use existing user
+ pass
+
+
+def create_users_and_tenants(identity_client,
+ config,
+ identity_section):
+ """
+ Creates the two non admin users and tenants for tempest
+ :param identity_client: openstack identity python client
+ :param config: tempest configuration file
+ :param identity_section: the section name of identity in the config
+ """
+ # Get the necessary params from the config file
+ tenant_name = config.get(identity_section, 'tenant_name')
+ username = config.get(identity_section, 'username')
+ password = config.get(identity_section, 'password')
+
+ alt_tenant_name = config.get(identity_section, 'alt_tenant_name')
+ alt_username = config.get(identity_section, 'alt_username')
+ alt_password = config.get(identity_section, 'alt_password')
+
+ # Create the necessary users for the test runs
+ create_user_with_tenant(identity_client, username, password, tenant_name)
+ create_user_with_tenant(identity_client, alt_username, alt_password,
+ alt_tenant_name)
+
+
+def main():
+ """
+ Main module to control the script
+ """
+ # TODO(tkammer): add support for existing config file
+ config_sample = getTempestConfigSample()
+ update_config_admin_credentials(config_sample, 'identity')
+
+ client_manager = ClientManager()
+
+ # Set the identity related info for tempest
+ identity_client_kwargs = get_identity_client_kwargs(config_sample,
+ 'identity')
+ identity_client = client_manager.get_identity_client(
+ **identity_client_kwargs)
+
+ # Create the necessary users and tenants for tempest run
+ create_users_and_tenants(identity_client,
+ config_sample,
+ 'identity')
+
+ # TODO(tkammer): add image implementation
+
+if __name__ == "__main__":
+ main()