588 lines
19 KiB
Python
588 lines
19 KiB
Python
#
|
|
# 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.
|
|
|
|
import copy
|
|
import mock
|
|
|
|
from heat.common import exception
|
|
from heat.engine import properties
|
|
from heat.engine import resource
|
|
from heat.engine.resources.openstack.keystone import role_assignments
|
|
from heat.engine import stack
|
|
from heat.engine import template
|
|
from heat.tests import common
|
|
from heat.tests import fakes
|
|
from heat.tests import generic_resource
|
|
from heat.tests import utils
|
|
|
|
RESOURCE_TYPE = 'OS::Keystone::DummyRoleAssignment'
|
|
|
|
keystone_role_assignment_template = {
|
|
'heat_template_version': '2015-10-15',
|
|
'resources': {
|
|
'test_role_assignment': {
|
|
'type': RESOURCE_TYPE,
|
|
'properties': {
|
|
'roles': [
|
|
{
|
|
'role': 'role_1',
|
|
'project': 'project_1',
|
|
},
|
|
{
|
|
'role': 'role_1',
|
|
'domain': 'domain_1'
|
|
}
|
|
]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
MixinClass = role_assignments.KeystoneRoleAssignmentMixin
|
|
|
|
|
|
class DummyRoleAssignment(generic_resource.GenericResource, MixinClass):
|
|
properties_schema = {}
|
|
properties_schema.update(MixinClass.mixin_properties_schema)
|
|
|
|
def validate(self):
|
|
super(DummyRoleAssignment, self).validate()
|
|
self.validate_assignment_properties()
|
|
|
|
|
|
class KeystoneRoleAssignmentMixinTest(common.HeatTestCase):
|
|
def setUp(self):
|
|
super(KeystoneRoleAssignmentMixinTest, self).setUp()
|
|
|
|
self.ctx = utils.dummy_context()
|
|
|
|
# For unit testing purpose. Register resource provider explicitly.
|
|
resource._register_class(RESOURCE_TYPE, DummyRoleAssignment)
|
|
|
|
self.stack = stack.Stack(
|
|
self.ctx, 'test_stack_keystone',
|
|
template.Template(keystone_role_assignment_template)
|
|
)
|
|
self.test_role_assignment = self.stack['test_role_assignment']
|
|
|
|
# Mock client
|
|
self.keystoneclient = mock.MagicMock()
|
|
self.test_role_assignment.client = mock.MagicMock()
|
|
self.test_role_assignment.client.return_value = self.keystoneclient
|
|
self.roles = self.keystoneclient.roles
|
|
|
|
# Mock client plugin
|
|
def _side_effect(value):
|
|
return value
|
|
|
|
self.keystone_client_plugin = mock.MagicMock()
|
|
(self.keystone_client_plugin.get_domain_id.
|
|
side_effect) = _side_effect
|
|
(self.keystone_client_plugin.get_role_id.
|
|
side_effect) = _side_effect
|
|
(self.keystone_client_plugin.get_project_id.
|
|
side_effect) = _side_effect
|
|
self.test_role_assignment.client_plugin = mock.MagicMock()
|
|
(self.test_role_assignment.client_plugin.
|
|
return_value) = self.keystone_client_plugin
|
|
|
|
def test_properties_title(self):
|
|
property_title_map = {MixinClass.ROLES: 'roles'}
|
|
|
|
for actual_title, expected_title in property_title_map.items():
|
|
self.assertEqual(
|
|
expected_title,
|
|
actual_title,
|
|
'KeystoneRoleAssignmentMixin PROPERTIES(%s) title modified.' %
|
|
actual_title)
|
|
|
|
def test_property_roles_validate_schema(self):
|
|
schema = MixinClass.mixin_properties_schema[MixinClass.ROLES]
|
|
self.assertEqual(
|
|
True,
|
|
schema.update_allowed,
|
|
'update_allowed for property %s is modified' %
|
|
MixinClass.ROLES)
|
|
|
|
self.assertEqual(properties.Schema.LIST,
|
|
schema.type,
|
|
'type for property %s is modified' %
|
|
MixinClass.ROLES)
|
|
|
|
self.assertEqual('List of role assignments.',
|
|
schema.description,
|
|
'description for property %s is modified' %
|
|
MixinClass.ROLES)
|
|
|
|
def test_role_assignment_create_user(self):
|
|
expected = [
|
|
{
|
|
'role': 'role_1',
|
|
'project': 'project_1',
|
|
'domain': None
|
|
}, {
|
|
'role': 'role_1',
|
|
'project': None,
|
|
'domain': 'domain_1'
|
|
}
|
|
]
|
|
# validate the properties
|
|
self.assertEqual(
|
|
expected,
|
|
self.test_role_assignment.properties.get(MixinClass.ROLES))
|
|
|
|
self.test_role_assignment.create_assignment(user_id='user_1')
|
|
|
|
# validate role assignment creation
|
|
# role-user-domain
|
|
self.roles.grant.assert_any_call(
|
|
role='role_1',
|
|
user='user_1',
|
|
domain='domain_1')
|
|
|
|
# role-user-project
|
|
self.roles.grant.assert_any_call(
|
|
role='role_1',
|
|
user='user_1',
|
|
project='project_1')
|
|
|
|
def test_role_assignment_create_group(self):
|
|
expected = [
|
|
{
|
|
'role': 'role_1',
|
|
'project': 'project_1',
|
|
'domain': None
|
|
}, {
|
|
'role': 'role_1',
|
|
'project': None,
|
|
'domain': 'domain_1'
|
|
}
|
|
]
|
|
# validate the properties
|
|
self.assertEqual(
|
|
expected,
|
|
self.test_role_assignment.properties.get(MixinClass.ROLES))
|
|
|
|
self.test_role_assignment.create_assignment(group_id='group_1')
|
|
|
|
# validate role assignment creation
|
|
# role-group-domain
|
|
self.roles.grant.assert_any_call(
|
|
role='role_1',
|
|
group='group_1',
|
|
domain='domain_1')
|
|
|
|
# role-group-project
|
|
self.roles.grant.assert_any_call(
|
|
role='role_1',
|
|
group='group_1',
|
|
project='project_1')
|
|
|
|
def test_role_assignment_update_user(self):
|
|
prop_diff = {
|
|
MixinClass.ROLES: [
|
|
{
|
|
'role': 'role_2',
|
|
'project': 'project_1'
|
|
},
|
|
{
|
|
'role': 'role_2',
|
|
'domain': 'domain_1'
|
|
}
|
|
]
|
|
}
|
|
|
|
self.test_role_assignment.update_assignment(
|
|
user_id='user_1',
|
|
prop_diff=prop_diff)
|
|
|
|
# Add role2-project1-domain1
|
|
# role-user-domain
|
|
self.roles.grant.assert_any_call(
|
|
role='role_2',
|
|
user='user_1',
|
|
domain='domain_1')
|
|
|
|
# role-user-project
|
|
self.roles.grant.assert_any_call(
|
|
role='role_2',
|
|
user='user_1',
|
|
project='project_1')
|
|
|
|
# Remove role1-project1-domain1
|
|
# role-user-domain
|
|
self.roles.revoke.assert_any_call(
|
|
role='role_1',
|
|
user='user_1',
|
|
domain='domain_1')
|
|
|
|
# role-user-project
|
|
self.roles.revoke.assert_any_call(
|
|
role='role_1',
|
|
user='user_1',
|
|
project='project_1')
|
|
|
|
def test_role_assignment_update_group(self):
|
|
prop_diff = {
|
|
MixinClass.ROLES: [
|
|
{
|
|
'role': 'role_2',
|
|
'project': 'project_1'
|
|
},
|
|
{
|
|
'role': 'role_2',
|
|
'domain': 'domain_1'
|
|
}
|
|
]
|
|
}
|
|
|
|
self.test_role_assignment.update_assignment(
|
|
group_id='group_1',
|
|
prop_diff=prop_diff)
|
|
|
|
# Add role2-project1-domain1
|
|
# role-group-domain
|
|
self.roles.grant.assert_any_call(
|
|
role='role_2',
|
|
group='group_1',
|
|
domain='domain_1')
|
|
|
|
# role-group-project
|
|
self.roles.grant.assert_any_call(
|
|
role='role_2',
|
|
group='group_1',
|
|
project='project_1')
|
|
|
|
# Remove role1-project1-domain1
|
|
# role-group-domain
|
|
self.roles.revoke.assert_any_call(
|
|
role='role_1',
|
|
group='group_1',
|
|
domain='domain_1')
|
|
|
|
# role-group-project
|
|
self.roles.revoke.assert_any_call(
|
|
role='role_1',
|
|
group='group_1',
|
|
project='project_1')
|
|
|
|
def test_role_assignment_update_roles_no_change(self):
|
|
prop_diff = {}
|
|
self.test_role_assignment.update_assignment(
|
|
group_id='group_1',
|
|
prop_diff=prop_diff)
|
|
self.assertEqual(0, self.roles.grant.call_count)
|
|
self.assertEqual(0, self.roles.revoke.call_count)
|
|
|
|
self.test_role_assignment.update_assignment(
|
|
user_id='user_1',
|
|
prop_diff=prop_diff)
|
|
self.assertEqual(0, self.roles.grant.call_count)
|
|
self.assertEqual(0, self.roles.revoke.call_count)
|
|
|
|
def test_role_assignment_delete_user(self):
|
|
self.assertIsNone(self.test_role_assignment.delete_assignment(
|
|
user_id='user_1'))
|
|
|
|
# Remove role1-project1-domain1
|
|
# role-user-domain
|
|
self.roles.revoke.assert_any_call(
|
|
role='role_1',
|
|
user='user_1',
|
|
domain='domain_1')
|
|
|
|
# role-user-project
|
|
self.roles.revoke.assert_any_call(
|
|
role='role_1',
|
|
user='user_1',
|
|
project='project_1')
|
|
|
|
def test_role_assignment_delete_group(self):
|
|
self.assertIsNone(self.test_role_assignment.delete_assignment(
|
|
group_id='group_1'
|
|
))
|
|
|
|
# Remove role1-project1-domain1
|
|
# role-group-domain
|
|
self.roles.revoke.assert_any_call(
|
|
role='role_1',
|
|
group='group_1',
|
|
domain='domain_1')
|
|
|
|
# role-group-project
|
|
self.roles.revoke.assert_any_call(
|
|
role='role_1',
|
|
group='group_1',
|
|
project='project_1')
|
|
|
|
def test_validate_1(self):
|
|
self.test_role_assignment.properties = mock.MagicMock()
|
|
|
|
# both project and domain are none
|
|
self.test_role_assignment.properties.get.return_value = [
|
|
dict(role='role1')]
|
|
self.assertRaises(exception.StackValidationFailed,
|
|
self.test_role_assignment.validate)
|
|
|
|
def test_validate_2(self):
|
|
self.test_role_assignment.properties = mock.MagicMock()
|
|
|
|
# both project and domain are not none
|
|
self.test_role_assignment.properties.get.return_value = [
|
|
dict(role='role1',
|
|
project='project1',
|
|
domain='domain1')
|
|
]
|
|
self.assertRaises(exception.ResourcePropertyConflict,
|
|
self.test_role_assignment.validate)
|
|
|
|
|
|
class KeystoneUserRoleAssignmentTest(common.HeatTestCase):
|
|
|
|
role_assignment_template = copy.deepcopy(keystone_role_assignment_template)
|
|
role = role_assignment_template['resources']['test_role_assignment']
|
|
role['properties']['user'] = 'user_1'
|
|
role['type'] = 'OS::Keystone::UserRoleAssignment'
|
|
|
|
def setUp(self):
|
|
super(KeystoneUserRoleAssignmentTest, self).setUp()
|
|
|
|
self.ctx = utils.dummy_context()
|
|
|
|
self.stack = stack.Stack(
|
|
self.ctx, 'test_stack_keystone_user_role_add',
|
|
template.Template(self.role_assignment_template)
|
|
)
|
|
self.test_role_assignment = self.stack['test_role_assignment']
|
|
|
|
# Mock client
|
|
self.keystoneclient = mock.Mock()
|
|
self.patchobject(resource.Resource, 'client',
|
|
return_value=fakes.FakeKeystoneClient(
|
|
client=self.keystoneclient))
|
|
self.roles = self.keystoneclient.roles
|
|
|
|
# Mock client plugin
|
|
def _side_effect(value):
|
|
return value
|
|
|
|
self.keystone_client_plugin = mock.MagicMock()
|
|
self.keystone_client_plugin.get_user_id.side_effect = _side_effect
|
|
self.keystone_client_plugin.get_domain_id.side_effect = _side_effect
|
|
self.keystone_client_plugin.get_role_id.side_effect = _side_effect
|
|
self.keystone_client_plugin.get_project_id.side_effect = _side_effect
|
|
self.test_role_assignment.client_plugin = mock.MagicMock()
|
|
(self.test_role_assignment.client_plugin.
|
|
return_value) = self.keystone_client_plugin
|
|
|
|
def test_user_role_assignment_handle_create(self):
|
|
self.test_role_assignment.handle_create()
|
|
|
|
# role-user-domain created
|
|
self.roles.grant.assert_any_call(
|
|
role='role_1',
|
|
user='user_1',
|
|
domain='domain_1')
|
|
|
|
# role-user-project created
|
|
self.roles.grant.assert_any_call(
|
|
role='role_1',
|
|
user='user_1',
|
|
project='project_1')
|
|
|
|
def test_user_role_assignment_handle_update(self):
|
|
prop_diff = {
|
|
MixinClass.ROLES: [
|
|
{
|
|
'role': 'role_2',
|
|
'project': 'project_1'
|
|
},
|
|
{
|
|
'role': 'role_2',
|
|
'domain': 'domain_1'
|
|
}
|
|
]
|
|
}
|
|
|
|
self.test_role_assignment.handle_update(json_snippet=None,
|
|
tmpl_diff=None,
|
|
prop_diff=prop_diff)
|
|
|
|
# Add role2-project1-domain1
|
|
# role-user-domain
|
|
self.roles.grant.assert_any_call(
|
|
role='role_2',
|
|
user='user_1',
|
|
domain='domain_1')
|
|
|
|
# role-user-project
|
|
self.roles.grant.assert_any_call(
|
|
role='role_2',
|
|
user='user_1',
|
|
project='project_1')
|
|
|
|
# Remove role1-project1-domain1
|
|
# role-user-domain
|
|
self.roles.revoke.assert_any_call(
|
|
role='role_1',
|
|
user='user_1',
|
|
domain='domain_1')
|
|
|
|
# role-user-project
|
|
self.roles.revoke.assert_any_call(
|
|
role='role_1',
|
|
user='user_1',
|
|
project='project_1')
|
|
|
|
def test_user_role_assignment_handle_delete(self):
|
|
self.assertIsNone(self.test_role_assignment.handle_delete())
|
|
|
|
# Remove role1-project1-domain1
|
|
# role-user-domain
|
|
self.roles.revoke.assert_any_call(
|
|
role='role_1',
|
|
user='user_1',
|
|
domain='domain_1')
|
|
|
|
# role-user-project
|
|
self.roles.revoke.assert_any_call(
|
|
role='role_1',
|
|
user='user_1',
|
|
project='project_1')
|
|
|
|
def test_user_role_assignment_delete_user_not_found(self):
|
|
self.keystone_client_plugin.get_user_id.side_effect = [
|
|
exception.EntityNotFound]
|
|
self.assertIsNone(self.test_role_assignment.handle_delete())
|
|
self.roles.revoke.assert_not_called()
|
|
|
|
|
|
class KeystoneGroupRoleAssignmentTest(common.HeatTestCase):
|
|
|
|
role_assignment_template = copy.deepcopy(keystone_role_assignment_template)
|
|
role = role_assignment_template['resources']['test_role_assignment']
|
|
role['properties']['group'] = 'group_1'
|
|
role['type'] = 'OS::Keystone::GroupRoleAssignment'
|
|
|
|
def setUp(self):
|
|
super(KeystoneGroupRoleAssignmentTest, self).setUp()
|
|
|
|
self.ctx = utils.dummy_context()
|
|
|
|
self.stack = stack.Stack(
|
|
self.ctx, 'test_stack_keystone_group_role_add',
|
|
template.Template(self.role_assignment_template)
|
|
)
|
|
self.test_role_assignment = self.stack['test_role_assignment']
|
|
|
|
# Mock client
|
|
self.keystoneclient = mock.Mock()
|
|
self.patchobject(resource.Resource, 'client',
|
|
return_value=fakes.FakeKeystoneClient(
|
|
client=self.keystoneclient))
|
|
self.roles = self.keystoneclient.roles
|
|
|
|
# Mock client plugin
|
|
def _side_effect(value):
|
|
return value
|
|
|
|
self.keystone_client_plugin = mock.MagicMock()
|
|
self.keystone_client_plugin.get_group_id.side_effect = _side_effect
|
|
self.keystone_client_plugin.get_domain_id.side_effect = _side_effect
|
|
self.keystone_client_plugin.get_role_id.side_effect = _side_effect
|
|
self.keystone_client_plugin.get_project_id.side_effect = _side_effect
|
|
self.test_role_assignment.client_plugin = mock.MagicMock()
|
|
(self.test_role_assignment.client_plugin.
|
|
return_value) = self.keystone_client_plugin
|
|
|
|
def test_group_role_assignment_handle_create(self):
|
|
self.test_role_assignment.handle_create()
|
|
|
|
# role-group-domain created
|
|
self.roles.grant.assert_any_call(
|
|
role='role_1',
|
|
group='group_1',
|
|
domain='domain_1')
|
|
|
|
# role-group-project created
|
|
self.roles.grant.assert_any_call(
|
|
role='role_1',
|
|
group='group_1',
|
|
project='project_1')
|
|
|
|
def test_group_role_assignment_handle_update(self):
|
|
prop_diff = {
|
|
MixinClass.ROLES: [
|
|
{
|
|
'role': 'role_2',
|
|
'project': 'project_1'
|
|
},
|
|
{
|
|
'role': 'role_2',
|
|
'domain': 'domain_1'
|
|
}
|
|
]
|
|
}
|
|
|
|
self.test_role_assignment.handle_update(json_snippet=None,
|
|
tmpl_diff=None,
|
|
prop_diff=prop_diff)
|
|
|
|
# Add role2-project1-domain1
|
|
# role-group-domain
|
|
self.roles.grant.assert_any_call(
|
|
role='role_2',
|
|
group='group_1',
|
|
domain='domain_1')
|
|
|
|
# role-group-project
|
|
self.roles.grant.assert_any_call(
|
|
role='role_2',
|
|
group='group_1',
|
|
project='project_1')
|
|
|
|
# Remove role1-project1-domain1
|
|
# role-group-domain
|
|
self.roles.revoke.assert_any_call(
|
|
role='role_1',
|
|
group='group_1',
|
|
domain='domain_1')
|
|
|
|
# role-group-project
|
|
self.roles.revoke.assert_any_call(
|
|
role='role_1',
|
|
group='group_1',
|
|
project='project_1')
|
|
|
|
def test_group_role_assignment_handle_delete(self):
|
|
self.assertIsNone(self.test_role_assignment.handle_delete())
|
|
|
|
# Remove role1-project1-domain1
|
|
# role-group-domain
|
|
self.roles.revoke.assert_any_call(
|
|
role='role_1',
|
|
group='group_1',
|
|
domain='domain_1')
|
|
|
|
# role-group-project
|
|
self.roles.revoke.assert_any_call(
|
|
role='role_1',
|
|
group='group_1',
|
|
project='project_1')
|
|
|
|
def test_group_role_assignment_delete_group_not_found(self):
|
|
self.keystone_client_plugin.get_group_id.side_effect = [
|
|
exception.EntityNotFound]
|
|
self.assertIsNone(self.test_role_assignment.handle_delete())
|
|
self.roles.revoke.assert_not_called()
|