heat_keystoneclient add legacy fallback path
If users fail to observe the reccomendations in the commit messages and/or release notes and use a heat.conf from a previous version of heat currently some functionality will fail due to the missing domain configuration in heat.conf. Instead degrade gracefully with warnings, calling the non-domain functions which are maintained (for now) for backwards compatibility. Hopefully we won't have to maintain this huge pile of legacy stuff for too long :( Change-Id: Ia029d585dfdb2563609725589c41083819a11a3b Closes-Bug: #1287980
This commit is contained in:
parent
0614b707d3
commit
7953ce1fec
|
@ -81,14 +81,25 @@ class KeystoneClientV3(object):
|
|||
# populates self.context.auth_token with a trust-scoped token
|
||||
self._client_v3 = self._v3_client_init()
|
||||
|
||||
# The stack domain user ID must be set in heat.conf
|
||||
# The stack domain user ID should be set in heat.conf
|
||||
# It can be created via python-openstackclient
|
||||
# openstack --os-identity-api-version=3 domain create heat
|
||||
stack_domain = cfg.CONF.stack_user_domain
|
||||
if not stack_domain:
|
||||
raise exception.Error("stack_user_domain ID not set in heat.conf")
|
||||
logger.debug(_("Using stack domain %s") % stack_domain)
|
||||
self.stack_domain_id = stack_domain
|
||||
# If the domain is specified, then you must specify a domain
|
||||
# admin user. If no domain is specified, we fall back to
|
||||
# legacy behavior with warnings.
|
||||
self.stack_domain_id = cfg.CONF.stack_user_domain
|
||||
self.domain_admin_user = cfg.CONF.stack_domain_admin
|
||||
self.domain_admin_password = cfg.CONF.stack_domain_admin_password
|
||||
if self.stack_domain_id:
|
||||
if not (self.domain_admin_user and self.domain_admin_password):
|
||||
raise exception.Error(_('heat.conf misconfigured, cannot '
|
||||
'specify stack_user_domain without'
|
||||
' stack_domain_admin and'
|
||||
' stack_domain_admin_password'))
|
||||
else:
|
||||
logger.warning(_('stack_user_domain ID not set in heat.conf '
|
||||
'falling back to using default'))
|
||||
logger.debug(_('Using stack domain %s') % self.stack_domain_id)
|
||||
|
||||
@property
|
||||
def client_v3(self):
|
||||
|
@ -183,14 +194,10 @@ class KeystoneClientV3(object):
|
|||
return creds
|
||||
|
||||
def _domain_admin_creds(self):
|
||||
domain_admin_user = cfg.CONF.stack_domain_admin
|
||||
if not domain_admin_user:
|
||||
raise ValueError('heat.conf misconfigured, '
|
||||
'no stack_domain_admin user specified')
|
||||
creds = {
|
||||
'username': domain_admin_user,
|
||||
'username': self.domain_admin_user,
|
||||
'user_domain_id': self.stack_domain_id,
|
||||
'password': cfg.CONF.stack_domain_admin_password,
|
||||
'password': self.domain_admin_password,
|
||||
'auth_url': self.v3_endpoint,
|
||||
'endpoint': self.v3_endpoint}
|
||||
return creds
|
||||
|
@ -309,6 +316,12 @@ class KeystoneClientV3(object):
|
|||
the specified project (which is expected to be in the stack_domain.
|
||||
Returns the keystone ID of the resulting user
|
||||
"""
|
||||
if not self.stack_domain_id:
|
||||
# FIXME(shardy): Legacy fallback for folks using old heat.conf
|
||||
# files which lack domain configuration
|
||||
logger.warning(_('Falling back to legacy non-domain user create, '
|
||||
'configure domain in heat.conf'))
|
||||
return self.create_stack_user(username=username, password=password)
|
||||
|
||||
# We add the new user to a special keystone role
|
||||
# This role is designed to allow easier differentiation of the
|
||||
|
@ -344,6 +357,12 @@ class KeystoneClientV3(object):
|
|||
raise ValueError(_('User %s in invalid project') % action)
|
||||
|
||||
def delete_stack_domain_user(self, user_id, project_id):
|
||||
if not self.stack_domain_id:
|
||||
# FIXME(shardy): Legacy fallback for folks using old heat.conf
|
||||
# files which lack domain configuration
|
||||
logger.warning(_('Falling back to legacy non-domain user delete, '
|
||||
'configure domain in heat.conf'))
|
||||
return self.delete_stack_user(user_id)
|
||||
self._check_stack_domain_user(user_id, project_id, 'delete')
|
||||
self.domain_admin_client.users.delete(user_id)
|
||||
|
||||
|
@ -352,6 +371,12 @@ class KeystoneClientV3(object):
|
|||
|
||||
def create_stack_domain_project(self, stack_name):
|
||||
'''Creates a project in the heat stack-user domain.'''
|
||||
if not self.stack_domain_id:
|
||||
# FIXME(shardy): Legacy fallback for folks using old heat.conf
|
||||
# files which lack domain configuration
|
||||
logger.warning(_('Falling back to legacy non-domain project, '
|
||||
'configure domain in heat.conf'))
|
||||
return self.context.tenant_id
|
||||
# Note we use the tenant ID not name to ensure uniqueness in a multi-
|
||||
# domain environment (where the tenant name may not be globally unique)
|
||||
project_name = '%s-%s' % (self.context.tenant_id, stack_name)
|
||||
|
@ -363,6 +388,12 @@ class KeystoneClientV3(object):
|
|||
return domain_project.id
|
||||
|
||||
def delete_stack_domain_project(self, project_id):
|
||||
if not self.stack_domain_id:
|
||||
# FIXME(shardy): Legacy fallback for folks using old heat.conf
|
||||
# files which lack domain configuration
|
||||
logger.warning(_('Falling back to legacy non-domain project, '
|
||||
'configure domain in heat.conf'))
|
||||
return
|
||||
self.domain_admin_client.projects.delete(project=project_id)
|
||||
|
||||
def _find_ec2_keypair(self, access, user_id=None):
|
||||
|
@ -424,6 +455,12 @@ class KeystoneClientV3(object):
|
|||
secret=data_blob['secret'])
|
||||
|
||||
def create_stack_domain_user_keypair(self, user_id, project_id):
|
||||
if not self.stack_domain_id:
|
||||
# FIXME(shardy): Legacy fallback for folks using old heat.conf
|
||||
# files which lack domain configuration
|
||||
logger.warning(_('Falling back to legacy non-domain keypair, '
|
||||
'configure domain in heat.conf'))
|
||||
return self.create_ec2_keypair(user_id)
|
||||
data_blob = {'access': uuid.uuid4().hex,
|
||||
'secret': uuid.uuid4().hex}
|
||||
creds = self.domain_admin_client.credentials.create(
|
||||
|
@ -440,10 +477,22 @@ class KeystoneClientV3(object):
|
|||
self.client_v3.users.update(user=user_id, enabled=True)
|
||||
|
||||
def disable_stack_domain_user(self, user_id, project_id):
|
||||
if not self.stack_domain_id:
|
||||
# FIXME(shardy): Legacy fallback for folks using old heat.conf
|
||||
# files which lack domain configuration
|
||||
logger.warning(_('Falling back to legacy non-domain disable, '
|
||||
'configure domain in heat.conf'))
|
||||
return self.disable_stack_user(user_id)
|
||||
self._check_stack_domain_user(user_id, project_id, 'disable')
|
||||
self.domain_admin_client.users.update(user=user_id, enabled=False)
|
||||
|
||||
def enable_stack_domain_user(self, user_id, project_id):
|
||||
if not self.stack_domain_id:
|
||||
# FIXME(shardy): Legacy fallback for folks using old heat.conf
|
||||
# files which lack domain configuration
|
||||
logger.warning(_('Falling back to legacy non-domain enable, '
|
||||
'configure domain in heat.conf'))
|
||||
return self.enable_stack_user(user_id)
|
||||
self._check_stack_domain_user(user_id, project_id, 'enable')
|
||||
self.domain_admin_client.users.update(user=user_id, enabled=True)
|
||||
|
||||
|
|
|
@ -218,6 +218,35 @@ class KeystoneClientTest(HeatTestCase):
|
|||
heat_ks_client.create_stack_domain_user(username='duser',
|
||||
project_id='aproject')
|
||||
|
||||
def test_create_stack_domain_user_legacy_fallback(self):
|
||||
"""Test creating a stack domain user, fallback path."""
|
||||
cfg.CONF.clear_override('stack_user_domain')
|
||||
|
||||
ctx = utils.dummy_context()
|
||||
ctx.trust_id = None
|
||||
|
||||
# mock keystone client functions
|
||||
self._stubs_v3()
|
||||
self.mock_ks_v3_client.users = self.m.CreateMockAnything()
|
||||
mock_user = self.m.CreateMockAnything()
|
||||
mock_user.id = 'auser123'
|
||||
self.mock_ks_v3_client.users.create(name='auser',
|
||||
password='password',
|
||||
default_project=ctx.tenant_id
|
||||
).AndReturn(mock_user)
|
||||
|
||||
self.mock_ks_v3_client.roles = self.m.CreateMockAnything()
|
||||
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)
|
||||
self.m.ReplayAll()
|
||||
|
||||
heat_ks_client = heat_keystoneclient.KeystoneClient(ctx)
|
||||
heat_ks_client.create_stack_domain_user(username='auser',
|
||||
project_id='aproject',
|
||||
password='password')
|
||||
|
||||
def test_create_stack_domain_user_error_norole(self):
|
||||
"""Test creating a stack domain user, no role error path."""
|
||||
ctx = utils.dummy_context()
|
||||
|
@ -258,6 +287,23 @@ class KeystoneClientTest(HeatTestCase):
|
|||
heat_ks_client.delete_stack_domain_user(user_id='duser123',
|
||||
project_id='aproject')
|
||||
|
||||
def test_delete_stack_domain_user_legacy_fallback(self):
|
||||
"""Test deleting a stack domain user, fallback path."""
|
||||
cfg.CONF.clear_override('stack_user_domain')
|
||||
|
||||
ctx = utils.dummy_context()
|
||||
ctx.trust_id = None
|
||||
|
||||
# mock keystone client functions
|
||||
self._stubs_v3()
|
||||
self.mock_ks_v3_client.users = self.m.CreateMockAnything()
|
||||
self.mock_ks_v3_client.users.delete(user='user123').AndReturn(None)
|
||||
self.m.ReplayAll()
|
||||
|
||||
heat_ks_client = heat_keystoneclient.KeystoneClient(ctx)
|
||||
heat_ks_client.delete_stack_domain_user(user_id='user123',
|
||||
project_id='aproject')
|
||||
|
||||
def test_delete_stack_domain_user_error_domain(self):
|
||||
"""Test deleting a stack domain user, wrong domain."""
|
||||
|
||||
|
@ -409,11 +455,26 @@ class KeystoneClientTest(HeatTestCase):
|
|||
self.assertEqual('atrust123', trust_context.trust_id)
|
||||
self.assertEqual('5678', trust_context.trustor_user_id)
|
||||
|
||||
def test_init_domain_cfg_not_set(self):
|
||||
def test_init_domain_cfg_not_set_fallback(self):
|
||||
|
||||
"""Test error path when config lacks stack domain ID."""
|
||||
"""Test error path when config lacks domain config."""
|
||||
|
||||
cfg.CONF.clear_override('stack_user_domain')
|
||||
cfg.CONF.clear_override('stack_domain_admin')
|
||||
cfg.CONF.clear_override('stack_domain_admin_password')
|
||||
|
||||
ctx = utils.dummy_context()
|
||||
ctx.username = None
|
||||
ctx.password = None
|
||||
ctx.trust_id = None
|
||||
self.assertIsNotNone(heat_keystoneclient.KeystoneClient(ctx))
|
||||
|
||||
def test_init_domain_cfg_not_set_error(self):
|
||||
|
||||
"""Test error path when config lacks domain config."""
|
||||
|
||||
cfg.CONF.clear_override('stack_domain_admin')
|
||||
cfg.CONF.clear_override('stack_domain_admin_password')
|
||||
|
||||
ctx = utils.dummy_context()
|
||||
ctx.username = None
|
||||
|
@ -421,7 +482,10 @@ class KeystoneClientTest(HeatTestCase):
|
|||
ctx.trust_id = None
|
||||
err = self.assertRaises(exception.Error,
|
||||
heat_keystoneclient.KeystoneClient, ctx)
|
||||
self.assertIn('stack_user_domain ID not set in heat.conf', err)
|
||||
exp_msg = ('heat.conf misconfigured, cannot specify stack_user_domain '
|
||||
'without stack_domain_admin and '
|
||||
'stack_domain_admin_password')
|
||||
self.assertIn(exp_msg, err)
|
||||
|
||||
def test_init_admin_client(self):
|
||||
|
||||
|
@ -627,6 +691,24 @@ class KeystoneClientTest(HeatTestCase):
|
|||
heat_ks_client.enable_stack_domain_user(user_id='duser123',
|
||||
project_id='aproject')
|
||||
|
||||
def test_enable_stack_domain_user_legacy_fallback(self):
|
||||
"""Test enabling a stack domain user, fallback path."""
|
||||
cfg.CONF.clear_override('stack_user_domain')
|
||||
|
||||
ctx = utils.dummy_context()
|
||||
ctx.trust_id = None
|
||||
|
||||
# mock keystone client functions
|
||||
self._stubs_v3()
|
||||
self.mock_ks_v3_client.users = self.m.CreateMockAnything()
|
||||
self.mock_ks_v3_client.users.update(user='user123', enabled=True
|
||||
).AndReturn(None)
|
||||
self.m.ReplayAll()
|
||||
|
||||
heat_ks_client = heat_keystoneclient.KeystoneClient(ctx)
|
||||
heat_ks_client.enable_stack_domain_user(user_id='user123',
|
||||
project_id='aproject')
|
||||
|
||||
def test_enable_stack_domain_user_error_project(self):
|
||||
"""Test enabling a stack domain user, wrong project."""
|
||||
|
||||
|
@ -674,6 +756,24 @@ class KeystoneClientTest(HeatTestCase):
|
|||
heat_ks_client.disable_stack_domain_user(user_id='duser123',
|
||||
project_id='aproject')
|
||||
|
||||
def test_disable_stack_domain_user_legacy_fallback(self):
|
||||
"""Test enabling a stack domain user, fallback path."""
|
||||
cfg.CONF.clear_override('stack_user_domain')
|
||||
|
||||
ctx = utils.dummy_context()
|
||||
ctx.trust_id = None
|
||||
|
||||
# mock keystone client functions
|
||||
self._stubs_v3()
|
||||
self.mock_ks_v3_client.users = self.m.CreateMockAnything()
|
||||
self.mock_ks_v3_client.users.update(user='user123', enabled=False
|
||||
).AndReturn(None)
|
||||
self.m.ReplayAll()
|
||||
|
||||
heat_ks_client = heat_keystoneclient.KeystoneClient(ctx)
|
||||
heat_ks_client.disable_stack_domain_user(user_id='user123',
|
||||
project_id='aproject')
|
||||
|
||||
def test_disable_stack_domain_user_error_project(self):
|
||||
"""Test disabling a stack domain user, wrong project."""
|
||||
|
||||
|
@ -783,6 +883,43 @@ class KeystoneClientTest(HeatTestCase):
|
|||
self.assertEqual('dummy_access2', ec2_cred.access)
|
||||
self.assertEqual('dummy_secret2', ec2_cred.secret)
|
||||
|
||||
def test_create_stack_domain_user_keypair_legacy_fallback(self):
|
||||
|
||||
"""Test creating ec2 credentials for domain user, fallback path."""
|
||||
cfg.CONF.clear_override('stack_user_domain')
|
||||
|
||||
self._stubs_v3()
|
||||
|
||||
ctx = utils.dummy_context()
|
||||
ctx.trust_id = None
|
||||
|
||||
ex_data = {'access': 'dummy_access2',
|
||||
'secret': 'dummy_secret2'}
|
||||
ex_data_json = json.dumps(ex_data)
|
||||
|
||||
# stub UUID.hex to match ex_data
|
||||
self._stub_uuid(['dummy_access2', 'dummy_secret2'])
|
||||
|
||||
# mock keystone client credentials functions
|
||||
self.mock_ks_v3_client.credentials = self.m.CreateMockAnything()
|
||||
mock_credential = self.m.CreateMockAnything()
|
||||
mock_credential.id = '1234567'
|
||||
mock_credential.user_id = 'atestuser2'
|
||||
mock_credential.blob = ex_data_json
|
||||
mock_credential.type = 'ec2'
|
||||
|
||||
# mock keystone client create function
|
||||
self.mock_ks_v3_client.credentials.create(
|
||||
user='atestuser2', type='ec2', data=ex_data_json,
|
||||
project=ctx.tenant_id).AndReturn(mock_credential)
|
||||
self.m.ReplayAll()
|
||||
heat_ks_client = heat_keystoneclient.KeystoneClient(ctx)
|
||||
ec2_cred = heat_ks_client.create_stack_domain_user_keypair(
|
||||
user_id='atestuser2', project_id='aproject')
|
||||
self.assertEqual('1234567', ec2_cred.id)
|
||||
self.assertEqual('dummy_access2', ec2_cred.access)
|
||||
self.assertEqual('dummy_secret2', ec2_cred.secret)
|
||||
|
||||
def test_get_ec2_keypair_id(self):
|
||||
|
||||
"""Test getting ec2 credential by id."""
|
||||
|
@ -936,6 +1073,18 @@ class KeystoneClientTest(HeatTestCase):
|
|||
heat_ks_client.create_stack_domain_project(
|
||||
stack_name='astack'))
|
||||
|
||||
def test_create_stack_domain_project_legacy_fallback(self):
|
||||
"""Test the create_stack_domain_project function, fallback path."""
|
||||
cfg.CONF.clear_override('stack_user_domain')
|
||||
|
||||
ctx = utils.dummy_context()
|
||||
ctx.trust_id = None
|
||||
|
||||
heat_ks_client = heat_keystoneclient.KeystoneClient(ctx)
|
||||
self.assertEqual(ctx.tenant_id,
|
||||
heat_ks_client.create_stack_domain_project(
|
||||
stack_name='astack'))
|
||||
|
||||
def test_delete_stack_domain_project(self):
|
||||
|
||||
"""Test the delete_stack_domain_project function."""
|
||||
|
@ -950,6 +1099,16 @@ class KeystoneClientTest(HeatTestCase):
|
|||
heat_ks_client = heat_keystoneclient.KeystoneClient(ctx)
|
||||
heat_ks_client.delete_stack_domain_project(project_id='aprojectid')
|
||||
|
||||
def test_delete_stack_domain_project_legacy_fallback(self):
|
||||
"""Test the delete_stack_domain_project function, fallback path."""
|
||||
cfg.CONF.clear_override('stack_user_domain')
|
||||
|
||||
ctx = utils.dummy_context()
|
||||
ctx.trust_id = None
|
||||
heat_ks_client = heat_keystoneclient.KeystoneClient(ctx)
|
||||
self.assertIsNone(heat_ks_client.delete_stack_domain_project(
|
||||
project_id='aprojectid'))
|
||||
|
||||
def _test_url_for(self, service_url, expected_kwargs, **kwargs):
|
||||
"""
|
||||
Helper function for testing url_for depending on different ways to
|
||||
|
|
Loading…
Reference in New Issue