heat_keystoneclient add create_stack_domain_user function

Add function which creates users in the heat domain user domain,
in the project specified (which is expected to be in the correct
domain, ie created via create_stack_domain_project)

This is similar to create_stack_user, which is expected to be removed
when all resources have been migrated to domain users.

Change-Id: Ie0a6ce039058a574ff0ddc3ed69e0e9cc9ad4db4
blueprint: instance-users
This commit is contained in:
Steven Hardy
2014-01-31 18:38:23 +00:00
parent 946064c066
commit c7d5485499
2 changed files with 118 additions and 37 deletions

View File

@@ -224,6 +224,23 @@ class KeystoneClient(object):
except kc_exception.NotFound:
pass
def _get_username(self, username):
if(len(username) > 64):
logger.warning(_("Truncating the username %s to the last 64 "
"characters.") % username)
#get the last 64 characters of the username
return username[-64:]
def _get_stack_user_role(self, roles_list):
# FIXME(shardy): The currently released v3 keystoneclient doesn't
# support filtering the results, so we have to do it locally,
# update when a new keystoneclient release happens containing
# the extensible-crud-manager-operations patch
stack_user_role = [r for r in roles_list
if r.name == self.conf.heat_stack_user_role]
if len(stack_user_role) == 1:
return stack_user_role[0].id
def create_stack_user(self, username, password=''):
"""
Create a user defined as part of a stack, either via template
@@ -231,30 +248,18 @@ class KeystoneClient(object):
the heat_stack_user_role as defined in the config
Returns the keystone ID of the resulting user
"""
if(len(username) > 64):
logger.warning(_("Truncating the username %s to the last 64 "
"characters.") % username)
#get the last 64 characters of the username
username = username[-64:]
# We add the new user to a special keystone role
# This role is designed to allow easier differentiation of the
# heat-generated "stack users" which will generally have credentials
# deployed on an instance (hence are implicitly untrusted)
# FIXME(shardy): The v3 keystoneclient doesn't currently support
# filtering the results, so we have to do it locally, update when
# that is fixed in keystoneclient
# FIXME(shardy): There's duplicated logic between here and
# create_stack_domain user, but this function is expected to
# be removed after the transition of all resources to domain
# users has been completed
roles_list = self.client_v3.roles.list()
stack_user_role = [r for r in roles_list
if r.name == self.conf.heat_stack_user_role]
if len(stack_user_role) == 1:
role_id = self._get_stack_user_role(roles_list)
if role_id:
# Create the user
user = self.client_v3.users.create(
name=username, password=password,
name=self._get_username(username), password=password,
default_project=self.context.tenant_id)
# Add user to heat_stack_user_role
role_id = stack_user_role[0].id
logger.debug(_("Adding user %(user)s to role %(role)s") % {
'user': user.id, 'role': role_id})
self.client_v3.roles.grant(role=role_id, user=user.id,
@@ -269,6 +274,40 @@ class KeystoneClient(object):
return user.id
def create_stack_domain_user(self, username, project_id, password=None):
"""
Create a user defined as part of a stack, either via template
or created internally by a resource. This user will be added to
the heat_stack_user_role as defined in the config, and created in
the specified project (which is expected to be in the stack_domain.
Returns the keystone ID of the resulting user
"""
# We add the new user to a special keystone role
# This role is designed to allow easier differentiation of the
# heat-generated "stack users" which will generally have credentials
# deployed on an instance (hence are implicitly untrusted)
roles_list = self.admin_client.roles.list()
role_id = self._get_stack_user_role(roles_list)
if role_id:
# Create user
user = self.admin_client.users.create(
name=self._get_username(username), password=password,
default_project=project_id, domain=self.stack_domain_id)
# Add to stack user role
logger.debug(_("Adding user %(user)s to role %(role)s") % {
'user': user.id, 'role': role_id})
self.admin_client.roles.grant(role=role_id, user=user.id,
project=project_id)
else:
logger.error(_("Failed to add user %(user)s to role %(role)s, "
"check role exists!") % {'user': username,
'role': self.conf.heat_stack_user_role})
raise exception.Error(_("Can't find role %s")
% self.conf.heat_stack_user_role)
return user.id
def delete_stack_user(self, user_id):
self.client_v3.users.delete(user=user_id)

View File

@@ -139,18 +139,9 @@ class KeystoneClientTest(HeatTestCase):
password='password',
default_project=ctx.tenant_id
).AndReturn(mock_user)
# mock out the call to roles; will send an error log message but does
# not raise an exception
self.mock_config.heat_stack_user_role = 'heat_stack_user'
mock_roles_list = []
for r_id, r_name in (('1234', 'blah'), ('4546', 'heat_stack_user')):
mock_role = self.m.CreateMockAnything()
mock_role.id = r_id
mock_role.name = r_name
mock_roles_list.append(mock_role)
self.mock_ks_v3_client.roles = self.m.CreateMockAnything()
self.mock_ks_v3_client.roles.list().AndReturn(mock_roles_list)
self.mock_ks_v3_client.roles.list().AndReturn(self._mock_roles_list())
self.mock_ks_v3_client.roles.grant(project=ctx.tenant_id,
role='4546',
user='auser123').AndReturn(None)
@@ -170,15 +161,8 @@ class KeystoneClientTest(HeatTestCase):
ctx = utils.dummy_context()
ctx.trust_id = None
self.mock_config.heat_stack_user_role = 'heat_stack_user'
mock_roles_list = []
for r_id, r_name in (('1234', 'blah'), ('4546', 'notheat_stack_user')):
mock_role = self.m.CreateMockAnything()
mock_role.id = r_id
mock_role.name = r_name
mock_roles_list.append(mock_role)
self.mock_ks_v3_client.roles = self.m.CreateMockAnything()
mock_roles_list = self._mock_roles_list(heat_stack_user='badrole')
self.mock_ks_v3_client.roles.list().AndReturn(mock_roles_list)
self.m.ReplayAll()
heat_ks_client = heat_keystoneclient.KeystoneClient(ctx)
@@ -187,6 +171,64 @@ class KeystoneClientTest(HeatTestCase):
'auser', password='password')
self.assertIn('Can\'t find role heat_stack_user', err)
def _mock_roles_list(self, heat_stack_user='heat_stack_user'):
self.mock_config.heat_stack_user_role = 'heat_stack_user'
mock_roles_list = []
for r_id, r_name in (('1234', 'blah'), ('4546', heat_stack_user)):
mock_role = self.m.CreateMockAnything()
mock_role.id = r_id
mock_role.name = r_name
mock_roles_list.append(mock_role)
return mock_roles_list
def test_create_stack_domain_user(self):
"""Test creating a stack domain user."""
ctx = utils.dummy_context()
ctx.trust_id = None
# mock keystone client functions
self._stub_domain(ret_id='adomain123')
self.mock_admin_client.users = self.m.CreateMockAnything()
mock_user = self.m.CreateMockAnything()
mock_user.id = 'duser123'
self.mock_admin_client.users.create(name='duser',
password=None,
default_project='aproject',
domain='adomain123'
).AndReturn(mock_user)
self.mock_admin_client.roles = self.m.CreateMockAnything()
self.mock_admin_client.roles.list().AndReturn(self._mock_roles_list())
self.mock_admin_client.roles.grant(project='aproject',
role='4546',
user='duser123').AndReturn(None)
self.m.ReplayAll()
heat_ks_client = heat_keystoneclient.KeystoneClient(ctx)
heat_ks_client.create_stack_domain_user(username='duser',
project_id='aproject')
def test_create_stack_domain_user_error_norole(self):
"""Test creating a stack domain user, no role error path."""
ctx = utils.dummy_context()
ctx.trust_id = None
self._stub_config()
self._stub_admin_client()
# mock keystone client functions
self.mock_admin_client.roles = self.m.CreateMockAnything()
mock_roles_list = self._mock_roles_list(heat_stack_user='badrole')
self.mock_admin_client.roles.list().AndReturn(mock_roles_list)
self.m.ReplayAll()
heat_ks_client = heat_keystoneclient.KeystoneClient(ctx)
err = self.assertRaises(exception.Error,
heat_ks_client.create_stack_domain_user,
username='duser', project_id='aproject')
self.assertIn('Can\'t find role heat_stack_user', err)
def test_delete_stack_user(self):
"""Test deleting a stack user."""