Prevent stale isolated tenants from blocking test runs
Especially while developing tests, it's easy to kill a test before
it has a chance to clean up the tenants it has created for isolation.
Since the database has a lot of consistency requirements, it's non-
trivial to go clean these up, and (for me) re-running stack.sh is
the quickest path to a running system again (which is annoying).
This patch adds a configuration option that can allow the tenant
creation to avoid the failure by re-using the existing tenant/user
already present. I think it's safe to have this enabled by default
since it only happens if tenant isolation is enabled.
Also, this augments the error message for the condition (in the case
that the configuration option is disabled) to better indicate
what is happening.
Change-Id: I97052b827ca1b2076ac67025539339b39d3260ae
diff --git a/etc/tempest.conf.sample b/etc/tempest.conf.sample
index ecd1e2a..8e7d8fb 100644
--- a/etc/tempest.conf.sample
+++ b/etc/tempest.conf.sample
@@ -33,6 +33,12 @@
# are known.
allow_tenant_isolation = true
+# Allows test cases to create/destroy tenants and users. This option
+# enables isolated test cases and better parallel execution,
+# but also requires that OpenStack Identity API admin credentials
+# are known.
+allow_tenant_reuse = true
+
# This should be the username of a user WITHOUT administrative privileges
username = demo
# The above non-administrative user's password
diff --git a/etc/tempest.conf.tpl b/etc/tempest.conf.tpl
index f268de2..ac1664a 100644
--- a/etc/tempest.conf.tpl
+++ b/etc/tempest.conf.tpl
@@ -29,6 +29,12 @@
# are known.
allow_tenant_isolation = %COMPUTE_ALLOW_TENANT_ISOLATION%
+# Allows test cases to create/destroy tenants and users. This option
+# enables isolated test cases and better parallel execution,
+# but also requires that OpenStack Identity API admin credentials
+# are known.
+allow_tenant_reuse = %COMPUTE_ALLOW_TENANT_REUSE%
+
# This should be the username of a user WITHOUT administrative privileges
username = %USERNAME%
# The above non-administrative user's password
diff --git a/tempest/config.py b/tempest/config.py
index 28a0c5e..003f03e 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -125,6 +125,17 @@
return self.get("allow_tenant_isolation", 'false').lower() != 'false'
@property
+ def allow_tenant_reuse(self):
+ """
+ If allow_tenant_isolation is True and a tenant that would be created
+ for a given test already exists (such as from a previously-failed run),
+ re-use that tenant instead of failing because of the conflict. Note
+ that this would result in the tenant being deleted at the end of a
+ subsequent successful run.
+ """
+ return self.get("allow_tenant_reuse", 'true').lower() != 'false'
+
+ @property
def username(self):
"""Username to use for Nova API requests."""
return self.get("username", "demo")
diff --git a/tempest/services/identity/json/admin_client.py b/tempest/services/identity/json/admin_client.py
index 89a068e..cb9c10b 100644
--- a/tempest/services/identity/json/admin_client.py
+++ b/tempest/services/identity/json/admin_client.py
@@ -101,6 +101,13 @@
body = json.loads(body)
return resp, body['tenants']
+ def get_tenant_by_name(self, tenant_name):
+ resp, tenants = self.list_tenants()
+ for tenant in tenants:
+ if tenant['name'] == tenant_name:
+ return tenant
+ raise exceptions.NotFound('No such tenant')
+
def update_tenant(self, tenant_id, **kwargs):
"""Updates a tenant"""
resp, body = self.get_tenant(tenant_id)
@@ -165,6 +172,13 @@
body = json.loads(body)
return resp, body['users']
+ def get_user_by_username(self, tenant_id, username):
+ resp, users = self.list_users_for_tenant(tenant_id)
+ for user in users:
+ if user['name'] == username:
+ return user
+ raise exceptions.NotFound('No such user')
+
def create_service(self, name, type, **kwargs):
"""Create a service"""
post_body = {
diff --git a/tempest/tests/compute/base.py b/tempest/tests/compute/base.py
index 414ced2..8b084ff 100644
--- a/tempest/tests/compute/base.py
+++ b/tempest/tests/compute/base.py
@@ -99,12 +99,37 @@
tenant_desc = tenant_name + "-desc"
password = "pass"
- resp, tenant = admin_client.create_tenant(name=tenant_name,
- description=tenant_desc)
- resp, user = admin_client.create_user(username,
- password,
- tenant['id'],
- email)
+ try:
+ resp, tenant = admin_client.create_tenant(name=tenant_name,
+ description=tenant_desc)
+ except exceptions.Duplicate:
+ if cls.config.compute.allow_tenant_reuse:
+ tenant = admin_client.get_tenant_by_name(tenant_name)
+ LOG.info('Re-using existing tenant %s' % tenant)
+ else:
+ msg = ('Unable to create isolated tenant %s because ' +
+ 'it already exists. If this is related to a ' +
+ 'previous test failure, try using ' +
+ 'allow_tentant_reuse in tempest.conf') % tenant_name
+ raise exceptions.Duplicate(msg)
+
+ try:
+ resp, user = admin_client.create_user(username,
+ password,
+ tenant['id'],
+ email)
+ except exceptions.Duplicate:
+ if cls.config.compute.allow_tenant_reuse:
+ user = admin_client.get_user_by_username(tenant['id'],
+ username)
+ LOG.info('Re-using existing user %s' % user)
+ else:
+ msg = ('Unable to create isolated tenant %s because ' +
+ 'it already exists. If this is related to a ' +
+ 'previous test failure, try using ' +
+ 'allow_tentant_reuse in tempest.conf') % tenant_name
+ raise exceptions.Duplicate(msg)
+
# Store the complete creds (including UUID ids...) for later
# but return just the username, tenant_name, password tuple
# that the various clients will use.