Support domain in keystone lookups
Implements: User, group, role and project lookup across domains. Added domain parameter to keystone lookup functions. Heat templates now support user{domain}, group{domain}, role{domain} and project{domain} to support cross domain lookup. Keystone constrains will also work across domain. Release note added. Story: 2005523 Task: 30642 Change-Id: I2b02787bd8883ced631b81174cee9134445bf170
This commit is contained in:
parent
c4076e12c8
commit
169a35e059
@ -11,6 +11,8 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import re
|
||||
|
||||
from keystoneauth1 import exceptions as ks_exceptions
|
||||
|
||||
from heat.common import exception
|
||||
@ -37,26 +39,59 @@ class KeystoneClientPlugin(client_plugin.ClientPlugin):
|
||||
def is_conflict(self, ex):
|
||||
return isinstance(ex, ks_exceptions.Conflict)
|
||||
|
||||
def get_role_id(self, role):
|
||||
def parse_entity_with_domain(self, entity_with_domain, entity_type):
|
||||
"""Parse keystone entity user/role/project with domain.
|
||||
|
||||
entity_with_domain should be in entity{domain} format.
|
||||
|
||||
Returns a tuple of (entity, domain).
|
||||
"""
|
||||
try:
|
||||
match = re.search(r"\{(.*?)\}$", entity_with_domain)
|
||||
if match:
|
||||
entity = entity_with_domain[:match.start()]
|
||||
domain = match.group(1)
|
||||
domain = self.get_domain_id(domain)
|
||||
return (entity, domain)
|
||||
else:
|
||||
return (entity_with_domain, None)
|
||||
except Exception:
|
||||
raise exception.EntityNotFound(entity=entity_type,
|
||||
name=entity_with_domain)
|
||||
|
||||
def get_role_id(self, role, domain=None):
|
||||
if role is None:
|
||||
return None
|
||||
|
||||
if not domain:
|
||||
role, domain = self.parse_entity_with_domain(role, 'KeystoneRole')
|
||||
|
||||
try:
|
||||
role_obj = self.client().client.roles.get(role)
|
||||
return role_obj.id
|
||||
except ks_exceptions.NotFound:
|
||||
role_list = self.client().client.roles.list(name=role)
|
||||
role_list = self.client().client.roles.list(name=role,
|
||||
domain=domain)
|
||||
for role_obj in role_list:
|
||||
if role_obj.name == role:
|
||||
return role_obj.id
|
||||
|
||||
raise exception.EntityNotFound(entity='KeystoneRole', name=role)
|
||||
|
||||
def get_project_id(self, project):
|
||||
def get_project_id(self, project, domain=None):
|
||||
if project is None:
|
||||
return None
|
||||
|
||||
if not domain:
|
||||
project, domain = self.parse_entity_with_domain(project,
|
||||
'KeystoneProject')
|
||||
|
||||
try:
|
||||
project_obj = self.client().client.projects.get(project)
|
||||
return project_obj.id
|
||||
except ks_exceptions.NotFound:
|
||||
project_list = self.client().client.projects.list(name=project)
|
||||
project_list = self.client().client.projects.list(name=project,
|
||||
domain=domain)
|
||||
for project_obj in project_list:
|
||||
if project_obj.name == project:
|
||||
return project_obj.id
|
||||
@ -78,14 +113,20 @@ class KeystoneClientPlugin(client_plugin.ClientPlugin):
|
||||
|
||||
raise exception.EntityNotFound(entity='KeystoneDomain', name=domain)
|
||||
|
||||
def get_group_id(self, group):
|
||||
def get_group_id(self, group, domain=None):
|
||||
if group is None:
|
||||
return None
|
||||
|
||||
if not domain:
|
||||
group, domain = self.parse_entity_with_domain(group,
|
||||
'KeystoneGroup')
|
||||
|
||||
try:
|
||||
group_obj = self.client().client.groups.get(group)
|
||||
return group_obj.id
|
||||
except ks_exceptions.NotFound:
|
||||
group_list = self.client().client.groups.list(name=group)
|
||||
group_list = self.client().client.groups.list(name=group,
|
||||
domain=domain)
|
||||
for group_obj in group_list:
|
||||
if group_obj.name == group:
|
||||
return group_obj.id
|
||||
@ -109,14 +150,20 @@ class KeystoneClientPlugin(client_plugin.ClientPlugin):
|
||||
raise exception.EntityNotFound(entity='KeystoneService',
|
||||
name=service)
|
||||
|
||||
def get_user_id(self, user):
|
||||
def get_user_id(self, user, domain=None):
|
||||
if user is None:
|
||||
return None
|
||||
|
||||
if not domain:
|
||||
user, domain = self.parse_entity_with_domain(user,
|
||||
'KeystoneUser')
|
||||
|
||||
try:
|
||||
user_obj = self.client().client.users.get(user)
|
||||
return user_obj.id
|
||||
except ks_exceptions.NotFound:
|
||||
user_list = self.client().client.users.list(name=user)
|
||||
user_list = self.client().client.users.list(name=user,
|
||||
domain=domain)
|
||||
for user_obj in user_list:
|
||||
if user_obj.name == user:
|
||||
return user_obj.id
|
||||
|
@ -21,6 +21,39 @@ from heat.engine.clients.os.keystone import keystone_constraints as ks_constr
|
||||
from heat.tests import common
|
||||
|
||||
|
||||
class KeystoneClientParseEntityTest(common.HeatTestCase):
|
||||
|
||||
sample_uuid = '477e8273-60a7-4c41-b683-fdb0bc7cd151'
|
||||
|
||||
@mock.patch.object(keystone.KeystoneClientPlugin, 'client')
|
||||
def test_parse_entity_with_domain(self, client_keystone):
|
||||
client_keystone.return_value = self._client
|
||||
client_plugin = keystone.KeystoneClientPlugin(
|
||||
context=mock.MagicMock()
|
||||
)
|
||||
client_plugin.get_domain_id = mock.MagicMock()
|
||||
client_plugin.get_domain_id.return_value = self.sample_uuid
|
||||
self.assertEqual(client_plugin.parse_entity_with_domain(
|
||||
'entity{domain}', 'entity_type'), ('entity', self.sample_uuid)
|
||||
)
|
||||
|
||||
@mock.patch.object(keystone.KeystoneClientPlugin, 'client')
|
||||
def test_parse_entity_without_domain(self, client_keystone):
|
||||
client_keystone.return_value = self._client
|
||||
client_plugin = keystone.KeystoneClientPlugin(
|
||||
context=mock.MagicMock()
|
||||
)
|
||||
client_plugin.get_domain_id = mock.MagicMock()
|
||||
client_plugin.get_domain_id.return_value = self.sample_uuid
|
||||
self.assertEqual(client_plugin.parse_entity_with_domain(
|
||||
'entity', 'entity_type'), ('entity', None)
|
||||
)
|
||||
|
||||
def setUp(self):
|
||||
super(KeystoneClientParseEntityTest, self).setUp()
|
||||
self._client = mock.MagicMock()
|
||||
|
||||
|
||||
class KeystoneRoleConstraintTest(common.HeatTestCase):
|
||||
|
||||
def test_expected_exceptions(self):
|
||||
@ -302,11 +335,16 @@ class KeystoneClientPluginRoleTest(common.HeatTestCase):
|
||||
|
||||
sample_uuid = '477e8273-60a7-4c41-b683-fdb0bc7cd152'
|
||||
sample_name = 'sample_role'
|
||||
sample_name_and_domain = 'sample_role{sample_domain}'
|
||||
sample_domain_uuid = '577e8273-60a7-4c41-b683-fdb0bc7cd152'
|
||||
sample_domain_name = 'sample_domain'
|
||||
sample_name_and_domain_invalid_input = 'sample_role@@'
|
||||
|
||||
def _get_mock_role(self):
|
||||
role = mock.MagicMock()
|
||||
role.id = self.sample_uuid
|
||||
role.name = self.sample_name
|
||||
role.name_and_domain = self.sample_name_and_domain
|
||||
return role
|
||||
|
||||
def setUp(self):
|
||||
@ -347,6 +385,29 @@ class KeystoneClientPluginRoleTest(common.HeatTestCase):
|
||||
self._client.client.roles.get,
|
||||
self.sample_name)
|
||||
self._client.client.roles.list.assert_called_once_with(
|
||||
domain=None, name=self.sample_name)
|
||||
|
||||
@mock.patch.object(keystone.KeystoneClientPlugin, 'client')
|
||||
def test_get_role_id_with_name_and_domain(self, client_keystone):
|
||||
self._client.client.roles.get.side_effect = (keystone_exceptions
|
||||
.NotFound)
|
||||
self._client.client.roles.list.return_value = [
|
||||
self._get_mock_role()
|
||||
]
|
||||
|
||||
client_keystone.return_value = self._client
|
||||
client_plugin = keystone.KeystoneClientPlugin(
|
||||
context=mock.MagicMock()
|
||||
)
|
||||
|
||||
self.assertEqual(self.sample_uuid, client_plugin.get_role_id(
|
||||
self.sample_name_and_domain))
|
||||
|
||||
self.assertRaises(keystone_exceptions.NotFound,
|
||||
self._client.client.roles.get,
|
||||
self.sample_name)
|
||||
self._client.client.roles.list.assert_called_once_with(
|
||||
domain=client_plugin.get_domain_id(self.sample_domain_uuid),
|
||||
name=self.sample_name)
|
||||
|
||||
@mock.patch.object(keystone.KeystoneClientPlugin, 'client')
|
||||
@ -371,18 +432,62 @@ class KeystoneClientPluginRoleTest(common.HeatTestCase):
|
||||
self._client.client.roles.get,
|
||||
self.sample_name)
|
||||
self._client.client.roles.list.assert_called_once_with(
|
||||
domain=None, name=self.sample_name)
|
||||
|
||||
@mock.patch.object(keystone.KeystoneClientPlugin, 'client')
|
||||
def test_get_role_id_with_domain_not_found(self, client_keystone):
|
||||
self._client.client.roles.get.side_effect = (keystone_exceptions
|
||||
.NotFound)
|
||||
self._client.client.roles.list.return_value = [
|
||||
]
|
||||
|
||||
client_keystone.return_value = self._client
|
||||
client_plugin = keystone.KeystoneClientPlugin(
|
||||
context=mock.MagicMock()
|
||||
)
|
||||
|
||||
ex = self.assertRaises(exception.EntityNotFound,
|
||||
client_plugin.get_role_id,
|
||||
self.sample_name_and_domain)
|
||||
msg = ("The KeystoneRole (%(name)s) could not be found." %
|
||||
{'name': self.sample_name})
|
||||
self.assertEqual(msg, six.text_type(ex))
|
||||
self.assertRaises(keystone_exceptions.NotFound,
|
||||
self._client.client.roles.get,
|
||||
self.sample_name)
|
||||
self._client.client.roles.list.assert_called_once_with(
|
||||
domain=client_plugin.get_domain_id(self.sample_domain_uuid),
|
||||
name=self.sample_name)
|
||||
|
||||
@mock.patch.object(keystone.KeystoneClientPlugin, 'client')
|
||||
def test_get_role_id_with_name_and_domain_invalid_input(self,
|
||||
client_keystone):
|
||||
self._client.client.roles.get.side_effect = (keystone_exceptions
|
||||
.NotFound)
|
||||
self._client.client.roles.list.return_value = []
|
||||
|
||||
client_keystone.return_value = self._client
|
||||
client_plugin = keystone.KeystoneClientPlugin(
|
||||
context=mock.MagicMock()
|
||||
)
|
||||
self.assertRaises(exception.EntityNotFound,
|
||||
client_plugin.get_role_id,
|
||||
self.sample_name_and_domain_invalid_input)
|
||||
|
||||
|
||||
class KeystoneClientPluginProjectTest(common.HeatTestCase):
|
||||
|
||||
sample_uuid = '477e8273-60a7-4c41-b683-fdb0bc7cd152'
|
||||
sample_name = 'sample_project'
|
||||
sample_name_and_domain = 'sample_project{sample_domain}'
|
||||
sample_domain_uuid = '577e8273-60a7-4c41-b683-fdb0bc7cd152'
|
||||
sample_domain_name = 'sample_domain'
|
||||
sample_name_and_domain_invalid_input = 'sample_project@@'
|
||||
|
||||
def _get_mock_project(self):
|
||||
project = mock.MagicMock()
|
||||
project.id = self.sample_uuid
|
||||
project.name = self.sample_name
|
||||
project.name_and_domain = self.sample_name_and_domain
|
||||
return project
|
||||
|
||||
def setUp(self):
|
||||
@ -423,6 +528,29 @@ class KeystoneClientPluginProjectTest(common.HeatTestCase):
|
||||
self._client.client.projects.get,
|
||||
self.sample_name)
|
||||
self._client.client.projects.list.assert_called_once_with(
|
||||
domain=None, name=self.sample_name)
|
||||
|
||||
@mock.patch.object(keystone.KeystoneClientPlugin, 'client')
|
||||
def test_get_project_id_with_name_and_domain(self, client_keystone):
|
||||
self._client.client.projects.get.side_effect = (keystone_exceptions
|
||||
.NotFound)
|
||||
self._client.client.projects.list.return_value = [
|
||||
self._get_mock_project()
|
||||
]
|
||||
|
||||
client_keystone.return_value = self._client
|
||||
client_plugin = keystone.KeystoneClientPlugin(
|
||||
context=mock.MagicMock()
|
||||
)
|
||||
|
||||
self.assertEqual(self.sample_uuid, client_plugin.get_project_id(
|
||||
self.sample_name_and_domain))
|
||||
|
||||
self.assertRaises(keystone_exceptions.NotFound,
|
||||
self._client.client.projects.get,
|
||||
self.sample_name)
|
||||
self._client.client.projects.list.assert_called_once_with(
|
||||
domain=client_plugin.get_domain_id(self.sample_domain_uuid),
|
||||
name=self.sample_name)
|
||||
|
||||
@mock.patch.object(keystone.KeystoneClientPlugin, 'client')
|
||||
@ -447,8 +575,47 @@ class KeystoneClientPluginProjectTest(common.HeatTestCase):
|
||||
self._client.client.projects.get,
|
||||
self.sample_name)
|
||||
self._client.client.projects.list.assert_called_once_with(
|
||||
domain=None, name=self.sample_name)
|
||||
|
||||
@mock.patch.object(keystone.KeystoneClientPlugin, 'client')
|
||||
def test_get_project_id_with_domain_not_found(self, client_keystone):
|
||||
self._client.client.projects.get.side_effect = (keystone_exceptions
|
||||
.NotFound)
|
||||
self._client.client.projects.list.return_value = []
|
||||
|
||||
client_keystone.return_value = self._client
|
||||
client_plugin = keystone.KeystoneClientPlugin(
|
||||
context=mock.MagicMock()
|
||||
)
|
||||
|
||||
ex = self.assertRaises(exception.EntityNotFound,
|
||||
client_plugin.get_project_id,
|
||||
self.sample_name_and_domain)
|
||||
msg = ("The KeystoneProject (%(name)s) could not be found." %
|
||||
{'name': self.sample_name})
|
||||
self.assertEqual(msg, six.text_type(ex))
|
||||
self.assertRaises(keystone_exceptions.NotFound,
|
||||
self._client.client.projects.get,
|
||||
self.sample_name)
|
||||
self._client.client.projects.list.assert_called_once_with(
|
||||
domain=client_plugin.get_domain_id(self.sample_domain_uuid),
|
||||
name=self.sample_name)
|
||||
|
||||
@mock.patch.object(keystone.KeystoneClientPlugin, 'client')
|
||||
def test_get_project_id_with_name_and_domain_invalid_input(
|
||||
self, client_keystone):
|
||||
self._client.client.projects.get.side_effect = (keystone_exceptions
|
||||
.NotFound)
|
||||
self._client.client.projects.list.return_value = []
|
||||
|
||||
client_keystone.return_value = self._client
|
||||
client_plugin = keystone.KeystoneClientPlugin(
|
||||
context=mock.MagicMock()
|
||||
)
|
||||
self.assertRaises(exception.EntityNotFound,
|
||||
client_plugin.get_project_id,
|
||||
self.sample_name_and_domain_invalid_input)
|
||||
|
||||
|
||||
class KeystoneClientPluginDomainTest(common.HeatTestCase):
|
||||
|
||||
@ -530,11 +697,16 @@ class KeystoneClientPluginGroupTest(common.HeatTestCase):
|
||||
|
||||
sample_uuid = '477e8273-60a7-4c41-b683-fdb0bc7cd152'
|
||||
sample_name = 'sample_group'
|
||||
sample_name_and_domain = 'sample_group{sample_domain}'
|
||||
sample_domain_uuid = '577e8273-60a7-4c41-b683-fdb0bc7cd152'
|
||||
sample_domain_name = 'sample_domain'
|
||||
sample_name_and_domain_invalid_input = 'sample_group@@'
|
||||
|
||||
def _get_mock_group(self):
|
||||
group = mock.MagicMock()
|
||||
group.id = self.sample_uuid
|
||||
group.name = self.sample_name
|
||||
group.name_and_domain = self.sample_name_and_domain
|
||||
return group
|
||||
|
||||
def setUp(self):
|
||||
@ -575,6 +747,29 @@ class KeystoneClientPluginGroupTest(common.HeatTestCase):
|
||||
self._client.client.groups.get,
|
||||
self.sample_name)
|
||||
self._client.client.groups.list.assert_called_once_with(
|
||||
domain=None, name=self.sample_name)
|
||||
|
||||
@mock.patch.object(keystone.KeystoneClientPlugin, 'client')
|
||||
def test_get_group_id_with_name_and_domain(self, client_keystone):
|
||||
self._client.client.groups.get.side_effect = (keystone_exceptions
|
||||
.NotFound)
|
||||
self._client.client.groups.list.return_value = [
|
||||
self._get_mock_group()
|
||||
]
|
||||
|
||||
client_keystone.return_value = self._client
|
||||
client_plugin = keystone.KeystoneClientPlugin(
|
||||
context=mock.MagicMock()
|
||||
)
|
||||
|
||||
self.assertEqual(self.sample_uuid, client_plugin.get_group_id(
|
||||
self.sample_name_and_domain))
|
||||
|
||||
self.assertRaises(keystone_exceptions.NotFound,
|
||||
self._client.client.groups.get,
|
||||
self.sample_name)
|
||||
self._client.client.groups.list.assert_called_once_with(
|
||||
domain=client_plugin.get_domain_id(self.sample_domain_uuid),
|
||||
name=self.sample_name)
|
||||
|
||||
@mock.patch.object(keystone.KeystoneClientPlugin, 'client')
|
||||
@ -599,18 +794,63 @@ class KeystoneClientPluginGroupTest(common.HeatTestCase):
|
||||
self._client.client.groups.get,
|
||||
self.sample_name)
|
||||
self._client.client.groups.list.assert_called_once_with(
|
||||
domain=None, name=self.sample_name)
|
||||
|
||||
@mock.patch.object(keystone.KeystoneClientPlugin, 'client')
|
||||
def test_get_group_id_with_domain_not_found(self, client_keystone):
|
||||
self._client.client.groups.get.side_effect = (keystone_exceptions
|
||||
.NotFound)
|
||||
self._client.client.groups.list.return_value = [
|
||||
]
|
||||
|
||||
client_keystone.return_value = self._client
|
||||
client_plugin = keystone.KeystoneClientPlugin(
|
||||
context=mock.MagicMock()
|
||||
)
|
||||
|
||||
ex = self.assertRaises(exception.EntityNotFound,
|
||||
client_plugin.get_group_id,
|
||||
self.sample_name_and_domain)
|
||||
msg = ("The KeystoneGroup (%(name)s) could not be found." %
|
||||
{'name': self.sample_name})
|
||||
self.assertEqual(msg, six.text_type(ex))
|
||||
self.assertRaises(keystone_exceptions.NotFound,
|
||||
self._client.client.groups.get,
|
||||
self.sample_name)
|
||||
self._client.client.groups.list.assert_called_once_with(
|
||||
domain=client_plugin.get_domain_id(self.sample_domain_uuid),
|
||||
name=self.sample_name)
|
||||
|
||||
@mock.patch.object(keystone.KeystoneClientPlugin, 'client')
|
||||
def test_get_group_id_with_name_and_domain_invalid_input(
|
||||
self, client_keystone):
|
||||
self._client.client.groups.get.side_effect = (keystone_exceptions
|
||||
.NotFound)
|
||||
self._client.client.groups.list.return_value = []
|
||||
|
||||
client_keystone.return_value = self._client
|
||||
client_plugin = keystone.KeystoneClientPlugin(
|
||||
context=mock.MagicMock()
|
||||
)
|
||||
self.assertRaises(exception.EntityNotFound,
|
||||
client_plugin.get_group_id,
|
||||
self.sample_name_and_domain_invalid_input)
|
||||
|
||||
|
||||
class KeystoneClientPluginUserTest(common.HeatTestCase):
|
||||
|
||||
sample_uuid = '477e8273-60a7-4c41-b683-fdb0bc7cd152'
|
||||
sample_name = 'sample_user'
|
||||
sample_name_and_domain = 'sample_user{sample_domain}'
|
||||
sample_domain_uuid = '577e8273-60a7-4c41-b683-fdb0bc7cd152'
|
||||
sample_domain_name = 'sample_domain'
|
||||
sample_name_and_domain_invalid_input = 'sample_user@@'
|
||||
|
||||
def _get_mock_user(self):
|
||||
user = mock.MagicMock()
|
||||
user.id = self.sample_uuid
|
||||
user.name = self.sample_name
|
||||
user.name_and_domain = self.sample_name_and_domain
|
||||
return user
|
||||
|
||||
def setUp(self):
|
||||
@ -620,7 +860,6 @@ class KeystoneClientPluginUserTest(common.HeatTestCase):
|
||||
@mock.patch.object(keystone.KeystoneClientPlugin, 'client')
|
||||
def test_get_user_id(self, client_keystone):
|
||||
self._client.client.users.get.return_value = self._get_mock_user()
|
||||
|
||||
client_keystone.return_value = self._client
|
||||
client_plugin = keystone.KeystoneClientPlugin(
|
||||
context=mock.MagicMock()
|
||||
@ -650,6 +889,27 @@ class KeystoneClientPluginUserTest(common.HeatTestCase):
|
||||
self._client.client.users.get,
|
||||
self.sample_name)
|
||||
self._client.client.users.list.assert_called_once_with(
|
||||
domain=None, name=self.sample_name)
|
||||
|
||||
@mock.patch.object(keystone.KeystoneClientPlugin, 'client')
|
||||
def test_get_user_id_with_name_and_domain(self, client_keystone):
|
||||
self._client.client.users.get.side_effect = (keystone_exceptions
|
||||
.NotFound)
|
||||
self._client.client.users.list.return_value = [
|
||||
self._get_mock_user()
|
||||
]
|
||||
|
||||
client_keystone.return_value = self._client
|
||||
client_plugin = keystone.KeystoneClientPlugin(
|
||||
context=mock.MagicMock())
|
||||
self.assertEqual(self.sample_uuid, client_plugin.get_user_id(
|
||||
self.sample_name_and_domain))
|
||||
|
||||
self.assertRaises(keystone_exceptions.NotFound,
|
||||
self._client.client.users.get,
|
||||
self.sample_name)
|
||||
self._client.client.users.list.assert_called_once_with(
|
||||
domain=client_plugin.get_domain_id(self.sample_domain_uuid),
|
||||
name=self.sample_name)
|
||||
|
||||
@mock.patch.object(keystone.KeystoneClientPlugin, 'client')
|
||||
@ -673,7 +933,22 @@ class KeystoneClientPluginUserTest(common.HeatTestCase):
|
||||
self._client.client.users.get,
|
||||
self.sample_name)
|
||||
self._client.client.users.list.assert_called_once_with(
|
||||
name=self.sample_name)
|
||||
domain=None, name=self.sample_name)
|
||||
|
||||
@mock.patch.object(keystone.KeystoneClientPlugin, 'client')
|
||||
def test_get_user_id_with_name_and_domain_invalid_input(self,
|
||||
client_keystone):
|
||||
self._client.client.users.get.side_effect = (keystone_exceptions
|
||||
.NotFound)
|
||||
self._client.client.users.list.return_value = []
|
||||
|
||||
client_keystone.return_value = self._client
|
||||
client_plugin = keystone.KeystoneClientPlugin(
|
||||
context=mock.MagicMock()
|
||||
)
|
||||
self.assertRaises(exception.EntityNotFound,
|
||||
client_plugin.get_user_id,
|
||||
self.sample_name_and_domain_invalid_input)
|
||||
|
||||
|
||||
class KeystoneClientPluginRegionTest(common.HeatTestCase):
|
||||
|
@ -0,0 +1,7 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
Supports user, group, role and project lookup across domains. Added domain
|
||||
parameter to keystone lookup functions. Heat templates now support
|
||||
user{domain}, group{domain}, role{domain} and project{domain} to support
|
||||
cross domain lookup. Keystone constrains will also work across domain.
|
Loading…
Reference in New Issue
Block a user