Merge "skip test_lock_unlock_server"
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/run_tests.sh b/run_tests.sh
index 970da51..5c8ce7d 100755
--- a/run_tests.sh
+++ b/run_tests.sh
@@ -63,7 +63,7 @@
-l|--logging) logging=1;;
-L|--logging-config) logging_config=$2; shift;;
--) [ "yes" == "$first_uu" ] || testrargs="$testrargs $1"; first_uu=no ;;
- *) testrargs="$testrargs $1"
+ *) testrargs="$testrargs $1"; noseargs+=" $1" ;;
esac
shift
done
diff --git a/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_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_quotas.py b/tempest/api/compute/admin/test_quotas.py
index f55f152..e744200 100644
--- a/tempest/api/compute/admin/test_quotas.py
+++ b/tempest/api/compute/admin/test_quotas.py
@@ -52,15 +52,6 @@
'instances', 'security_group_rules',
'cores', 'security_groups'))
- @classmethod
- def tearDownClass(cls):
- for server in cls.servers:
- try:
- cls.servers_client.delete_server(server['id'])
- except exceptions.NotFound:
- continue
- super(QuotasAdminTestJSON, cls).tearDownClass()
-
@attr(type='smoke')
def test_get_default_quotas(self):
# Admin can get the default resource quota set for a tenant
diff --git a/tempest/api/compute/admin/test_servers.py b/tempest/api/compute/admin/test_servers.py
index e730d31..97e0e69 100644
--- a/tempest/api/compute/admin/test_servers.py
+++ b/tempest/api/compute/admin/test_servers.py
@@ -19,6 +19,7 @@
from tempest.common.utils.data_utils import rand_name
from tempest import exceptions
from tempest.test import attr
+from tempest.test import skip_because
class ServersAdminTestJSON(base.BaseComputeAdminTest):
@@ -33,10 +34,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 +159,25 @@
self.assertRaises(exceptions.NotFound,
self.client.reset_state, '999')
+ @attr(type='gate')
+ @skip_because(bug="1240043")
+ def test_get_server_diagnostics_by_admin(self):
+ # Retrieve server diagnostics by admin user
+ resp, diagnostic = self.client.get_server_diagnostics(self.s1_id)
+ 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_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 6e8afa0..255beb7 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/network/base.py b/tempest/api/network/base.py
index cfac257..c3a66c5 100644
--- a/tempest/api/network/base.py
+++ b/tempest/api/network/base.py
@@ -56,6 +56,7 @@
cls.networks = []
cls.subnets = []
cls.ports = []
+ cls.routers = []
cls.pools = []
cls.vips = []
cls.members = []
@@ -73,6 +74,8 @@
cls.client.delete_pool(pool['id'])
for port in cls.ports:
cls.client.delete_port(port['id'])
+ for router in cls.routers:
+ cls.client.delete_router(router['id'])
for subnet in cls.subnets:
cls.client.delete_subnet(subnet['id'])
for network in cls.networks:
@@ -125,6 +128,21 @@
return port
@classmethod
+ def create_router(cls, router_name=None, admin_state_up=False,
+ external_network_id=None, enable_snat=None):
+ ext_gw_info = {}
+ if external_network_id:
+ ext_gw_info['network_id'] = external_network_id
+ if enable_snat:
+ ext_gw_info['enable_snat'] = enable_snat
+ resp, body = cls.client.create_router(
+ router_name, external_gateway_info=ext_gw_info,
+ admin_state_up=admin_state_up)
+ router = body['router']
+ cls.routers.append(router)
+ return router
+
+ @classmethod
def create_pool(cls, name, lb_method, protocol, subnet):
"""Wrapper utility that returns a test pool."""
resp, body = cls.client.create_pool(name, lb_method, protocol,
diff --git a/tempest/api/network/test_floating_ips.py b/tempest/api/network/test_floating_ips.py
index ca2c879..9acb6c5 100644
--- a/tempest/api/network/test_floating_ips.py
+++ b/tempest/api/network/test_floating_ips.py
@@ -47,11 +47,9 @@
# Create network, subnet, router and add interface
cls.network = cls.create_network()
cls.subnet = cls.create_subnet(cls.network)
- resp, router = cls.client.create_router(
+ cls.router = cls.create_router(
rand_name('router-'),
- external_gateway_info={"network_id":
- cls.network_cfg.public_network_id})
- cls.router = router['router']
+ external_network_id=cls.network_cfg.public_network_id)
resp, _ = cls.client.add_router_interface_with_subnet_id(
cls.router['id'], cls.subnet['id'])
cls.port = list()
@@ -66,7 +64,6 @@
cls.subnet['id'])
for i in range(2):
cls.client.delete_port(cls.port[i]['id'])
- cls.client.delete_router(cls.router['id'])
super(FloatingIPTestJSON, cls).tearDownClass()
def _delete_floating_ip(self, floating_ip_id):
diff --git a/tempest/api/network/test_routers.py b/tempest/api/network/test_routers.py
index 8b939fe..2cfbf61 100644
--- a/tempest/api/network/test_routers.py
+++ b/tempest/api/network/test_routers.py
@@ -52,6 +52,8 @@
@attr(type='smoke')
def test_create_show_list_update_delete_router(self):
# Create a router
+ # NOTE(salv-orlando): Do not invoke self.create_router
+ # as we need to check the response code
name = rand_name('router-')
resp, create_body = self.client.create_router(
name, external_gateway_info={
@@ -94,41 +96,37 @@
def test_add_remove_router_interface_with_subnet_id(self):
network = self.create_network()
subnet = self.create_subnet(network)
- name = rand_name('router-')
- resp, create_body = self.client.create_router(name)
- self.addCleanup(self.client.delete_router, create_body['router']['id'])
+ router = self.create_router(rand_name('router-'))
# Add router interface with subnet id
resp, interface = self.client.add_router_interface_with_subnet_id(
- create_body['router']['id'], subnet['id'])
+ router['id'], subnet['id'])
self.assertEqual('200', resp['status'])
self.addCleanup(self._remove_router_interface_with_subnet_id,
- create_body['router']['id'], subnet['id'])
+ router['id'], subnet['id'])
self.assertTrue('subnet_id' in interface.keys())
self.assertTrue('port_id' in interface.keys())
# Verify router id is equal to device id in port details
resp, show_port_body = self.client.show_port(
interface['port_id'])
self.assertEqual(show_port_body['port']['device_id'],
- create_body['router']['id'])
+ router['id'])
@attr(type='smoke')
def test_add_remove_router_interface_with_port_id(self):
network = self.create_network()
self.create_subnet(network)
- name = rand_name('router-')
- resp, create_body = self.client.create_router(name)
- self.addCleanup(self.client.delete_router, create_body['router']['id'])
+ router = self.create_router(rand_name('router-'))
resp, port_body = self.client.create_port(network['id'])
# add router interface to port created above
resp, interface = self.client.add_router_interface_with_port_id(
- create_body['router']['id'], port_body['port']['id'])
+ router['id'], port_body['port']['id'])
self.assertEqual('200', resp['status'])
self.addCleanup(self._remove_router_interface_with_port_id,
- create_body['router']['id'], port_body['port']['id'])
+ router['id'], port_body['port']['id'])
self.assertTrue('subnet_id' in interface.keys())
self.assertTrue('port_id' in interface.keys())
# Verify router id is equal to device id in port details
resp, show_port_body = self.client.show_port(
interface['port_id'])
self.assertEqual(show_port_body['port']['device_id'],
- create_body['router']['id'])
+ router['id'])
diff --git a/tempest/api/network/test_security_groups.py b/tempest/api/network/test_security_groups.py
index 60ca88a..914dcff 100644
--- a/tempest/api/network/test_security_groups.py
+++ b/tempest/api/network/test_security_groups.py
@@ -16,7 +16,7 @@
# under the License.
from tempest.api.network import base
-from tempest.common.utils.data_utils import rand_name
+from tempest.common.utils import data_utils
from tempest import exceptions
from tempest.test import attr
@@ -66,9 +66,9 @@
self.assertIsNotNone(found, msg)
@attr(type='smoke')
- def test_create_show_delete_security_group_and_rule(self):
+ def test_create_show_delete_security_group(self):
# Create a security group
- name = rand_name('secgroup-')
+ name = data_utils.rand_name('secgroup-')
resp, group_create_body = self.client.create_security_group(name)
self.assertEqual('201', resp['status'])
self.addCleanup(self._delete_security_group,
@@ -88,14 +88,29 @@
for secgroup in list_body['security_groups']:
secgroup_list.append(secgroup['id'])
self.assertIn(group_create_body['security_group']['id'], secgroup_list)
- # No Update in security group
- # Create rule
- resp, rule_create_body = self.client.create_security_group_rule(
- group_create_body['security_group']['id']
- )
+
+ @attr(type='smoke')
+ def test_create_show_delete_security_group_rule(self):
+ # Create a security group
+ name = data_utils.rand_name('secgroup-')
+ resp, group_create_body = self.client.create_security_group(name)
self.assertEqual('201', resp['status'])
- self.addCleanup(self._delete_security_group_rule,
- rule_create_body['security_group_rule']['id'])
+ self.addCleanup(self._delete_security_group,
+ group_create_body['security_group']['id'])
+ self.assertEqual(group_create_body['security_group']['name'], name)
+
+ # Create rules for each protocol
+ protocols = ['tcp', 'udp', 'icmp']
+ for protocol in protocols:
+ resp, rule_create_body = self.client.create_security_group_rule(
+ group_create_body['security_group']['id'],
+ protocol=protocol
+ )
+ self.assertEqual('201', resp['status'])
+ self.addCleanup(self._delete_security_group_rule,
+ rule_create_body['security_group_rule']['id']
+ )
+
# Show details of the created security rule
resp, show_rule_body = self.client.show_security_group_rule(
rule_create_body['security_group_rule']['id']
@@ -111,13 +126,13 @@
@attr(type=['negative', 'smoke'])
def test_show_non_existent_security_group(self):
- non_exist_id = rand_name('secgroup-')
+ non_exist_id = data_utils.rand_name('secgroup-')
self.assertRaises(exceptions.NotFound, self.client.show_security_group,
non_exist_id)
@attr(type=['negative', 'smoke'])
def test_show_non_existent_security_group_rule(self):
- non_exist_id = rand_name('rule-')
+ non_exist_id = data_utils.rand_name('rule-')
self.assertRaises(exceptions.NotFound,
self.client.show_security_group_rule,
non_exist_id)
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/tests/test_wrappers.py b/tempest/tests/test_wrappers.py
index 1a5af00..dbe3420 100644
--- a/tempest/tests/test_wrappers.py
+++ b/tempest/tests/test_wrappers.py
@@ -20,8 +20,6 @@
import tempfile
import testtools
-from tempest.test import attr
-
DEVNULL = open(os.devnull, 'wb')
@@ -46,7 +44,6 @@
shutil.copy('tempest/tests/files/setup.cfg', self.setup_cfg_file)
shutil.copy('tempest/tests/files/__init__.py', self.init_file)
- @attr(type='smoke')
def test_pretty_tox(self):
# Copy wrapper script and requirements:
pretty_tox = os.path.join(self.directory, 'pretty_tox.sh')
@@ -62,7 +59,6 @@
shell=True, stdout=DEVNULL, stderr=DEVNULL)
self.assertEqual(exit_code, 0)
- @attr(type='smoke')
def test_pretty_tox_fails(self):
# Copy wrapper script and requirements:
pretty_tox = os.path.join(self.directory, 'pretty_tox.sh')
@@ -78,7 +74,6 @@
stdout=DEVNULL, stderr=DEVNULL)
self.assertEqual(exit_code, 1)
- @attr(type='smoke')
def test_pretty_tox_serial(self):
# Copy wrapper script and requirements:
pretty_tox = os.path.join(self.directory, 'pretty_tox_serial.sh')
@@ -90,7 +85,6 @@
shell=True, stdout=DEVNULL, stderr=DEVNULL)
self.assertEqual(exit_code, 0)
- @attr(type='smoke')
def test_pretty_tox_serial_fails(self):
# Copy wrapper script and requirements:
pretty_tox = os.path.join(self.directory, 'pretty_tox_serial.sh')
diff --git a/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()
diff --git a/tox.ini b/tox.ini
index ff09b3f..d93112c 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,11 +1,23 @@
[tox]
envlist = pep8
+minversion = 1.6
+skipsdist = True
[testenv]
setenv = VIRTUAL_ENV={envdir}
LANG=en_US.UTF-8
LANGUAGE=en_US:en
LC_ALL=C
+usedevelop = True
+
+[testenv:py26]
+commands = python setup.py test --slowest --testr-arg='tempest\.tests {posargs}'
+
+[testenv:py33]
+commands = python setup.py test --slowest --testr-arg='tempest\.tests {posargs}'
+
+[testenv:py27]
+commands = python setup.py test --slowest --testr-arg='tempest\.tests {posargs}'
[testenv:all]
sitepackages = True
@@ -15,29 +27,25 @@
[testenv:full]
sitepackages = True
-setenv = VIRTUAL_ENV={envdir}
# The regex below is used to select which tests to run and exclude the slow tag:
# See the testrepostiory bug: https://bugs.launchpad.net/testrepository/+bug/1208610
commands =
- sh tools/pretty_tox.sh '(?!.*\[.*\bslow\b.*\])(^tempest\.(api|scenario|thirdparty|cli|tests)) {posargs}'
+ sh tools/pretty_tox.sh '(?!.*\[.*\bslow\b.*\])(^tempest\.(api|scenario|thirdparty|cli)) {posargs}'
[testenv:testr-full]
sitepackages = True
-setenv = VIRTUAL_ENV={envdir}
commands =
- sh tools/pretty_tox.sh '(?!.*\[.*\bslow\b.*\])(^tempest\.(api|scenario|thirdparty|cli|tests)) {posargs}'
+ sh tools/pretty_tox.sh '(?!.*\[.*\bslow\b.*\])(^tempest\.(api|scenario|thirdparty|cli)) {posargs}'
[testenv:heat-slow]
sitepackages = True
-setenv = VIRTUAL_ENV={envdir}
- OS_TEST_TIMEOUT=1200
+setenv = OS_TEST_TIMEOUT=1200
# The regex below is used to select heat api/scenario tests tagged as slow.
commands =
sh tools/pretty_tox_serial.sh '(?=.*\[.*\bslow\b.*\])(^tempest\.(api|scenario)\.orchestration) {posargs}'
[testenv:large-ops]
sitepackages = True
-setenv = VIRTUAL_ENV={envdir}
commands =
python setup.py testr --slowest --testr-args='tempest.scenario.test_large_ops {posargs}'
@@ -53,7 +61,7 @@
NOSE_OPENSTACK_STDOUT=1
TEMPEST_PY26_NOSE_COMPAT=1
commands =
- nosetests --logging-format '%(asctime)-15s %(message)s' --with-xunit -sv --xunit-file=nosetests-full.xml tempest/api tempest/scenario tempest/thirdparty tempest/cli tempest/tests {posargs}
+ nosetests --logging-format '%(asctime)-15s %(message)s' --with-xunit -sv --xunit-file=nosetests-full.xml tempest/api tempest/scenario tempest/thirdparty tempest/cli {posargs}
[testenv:py26-smoke]
setenv = VIRTUAL_ENV={envdir}
@@ -69,7 +77,6 @@
[testenv:smoke]
sitepackages = True
-setenv = VIRTUAL_ENV={envdir}
# This is still serial because neutron doesn't work with parallel. See:
# https://bugs.launchpad.net/tempest/+bug/1216076 so the neutron smoke
# job would fail if we moved it to parallel.
@@ -78,15 +85,13 @@
[testenv:coverage]
sitepackages = True
-setenv = VIRTUAL_ENV={envdir}
commands =
python -m tools/tempest_coverage -c start --combine
- sh tools/pretty_tox.sh '(?!.*\[.*\bslow\b.*\])(^tempest\.(api|scenario|thirdparty|cli|tests))'
+ sh tools/pretty_tox.sh '(?!.*\[.*\bslow\b.*\])(^tempest\.(api|scenario|thirdparty|cli))'
python -m tools/tempest_coverage -c report --html {posargs}
[testenv:stress]
sitepackages = True
-setenv = VIRTUAL_ENV={envdir}
commands =
python -m tempest/stress/run_stress -a -d 3600 -S