Merge "Add a create_router utility function"
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_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..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_server_actions.py b/tempest/api/compute/servers/test_server_actions.py
index 6f646b2..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.')
@@ -286,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/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_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 ff0cddb..db923e9 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -410,7 +410,7 @@
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."),
]
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/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/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/tox.ini b/tox.ini
index ff09b3f..cf41586 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,11 +1,14 @@
[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:all]
sitepackages = True
@@ -15,7 +18,6 @@
[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 =
@@ -23,21 +25,18 @@
[testenv:testr-full]
sitepackages = True
-setenv = VIRTUAL_ENV={envdir}
commands =
sh tools/pretty_tox.sh '(?!.*\[.*\bslow\b.*\])(^tempest\.(api|scenario|thirdparty|cli|tests)) {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}'
@@ -69,7 +68,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,7 +76,6 @@
[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))'
@@ -86,7 +83,6 @@
[testenv:stress]
sitepackages = True
-setenv = VIRTUAL_ENV={envdir}
commands =
python -m tempest/stress/run_stress -a -d 3600 -S