Merge "Add db operations for resource members"

This commit is contained in:
Jenkins 2016-02-01 07:59:38 +00:00 committed by Gerrit Code Review
commit cc3e106a06
4 changed files with 368 additions and 2 deletions

View File

@ -450,3 +450,35 @@ def delete_environment(name):
def delete_environments(**kwargs):
IMPL.delete_environments(**kwargs)
# Resource members.
def create_resource_member(values):
return IMPL.create_resource_member(values)
def get_resource_member(resource_id, res_type, member_id):
return IMPL.get_resource_member(resource_id, res_type, member_id)
def get_resource_members(resource_id, res_type):
return IMPL.get_resource_members(resource_id, res_type)
def update_resource_member(resource_id, res_type, member_id, values):
return IMPL.update_resource_member(
resource_id,
res_type,
member_id,
values
)
def delete_resource_member(resource_id, res_type, member_id):
IMPL.delete_resource_member(resource_id, res_type, member_id)
def delete_resource_members(**kwargs):
IMPL.delete_resource_members(**kwargs)

View File

@ -148,7 +148,13 @@ def _delete_all(model, session=None, **kwargs):
def _get_collection_sorted_by_name(model, **kwargs):
return _secure_query(model).filter_by(**kwargs).order_by(model.name).all()
# Note(lane): Sometimes tenant_A needs to get resources of tenant_B,
# especially in resource sharing scenario, the resource owner needs to
# check if the resource is used by a member.
query = (b.model_query(model) if 'project_id' in kwargs
else _secure_query(model))
return query.filter_by(**kwargs).order_by(model.name).all()
def _get_collection_sorted_by_time(model, **kwargs):
@ -1180,3 +1186,143 @@ def _get_environment(name):
@b.session_aware()
def delete_environments(**kwargs):
return _delete_all(models.Environment, **kwargs)
# Resource members.
def _get_criterion(resource_id, member_id=None, is_owner=True):
"""Generates criterion for querying resource_member_v2 table."""
# Resource owner query resource membership with member_id.
if is_owner and member_id:
return sa.and_(
models.ResourceMember.project_id == security.get_project_id(),
models.ResourceMember.resource_id == resource_id,
models.ResourceMember.member_id == member_id
)
# Resource owner query resource memberships.
elif is_owner and not member_id:
return sa.and_(
models.ResourceMember.project_id == security.get_project_id(),
models.ResourceMember.resource_id == resource_id,
)
# Other members query other resource membership.
elif not is_owner and member_id and member_id != security.get_project_id():
return None
# Resource member query resource memberships.
return sa.and_(
models.ResourceMember.member_id == security.get_project_id(),
models.ResourceMember.resource_id == resource_id
)
@b.session_aware()
def create_resource_member(values, session=None):
res_member = models.ResourceMember()
res_member.update(values.copy())
try:
res_member.save(session=session)
except db_exc.DBDuplicateEntry as e:
raise exc.DBDuplicateEntryException(
"Duplicate entry for ResourceMember: %s" % e.columns
)
return res_member
def get_resource_member(resource_id, res_type, member_id):
query = _secure_query(models.ResourceMember).filter_by(
resource_type=res_type
)
# Both resource owner and resource member can do query.
res_member = query.filter(
sa.or_(
_get_criterion(resource_id, member_id),
_get_criterion(resource_id, member_id, is_owner=False)
)
).first()
if not res_member:
raise exc.NotFoundException(
"Resource member not found [resource_id=%s, member_id=%s]" %
(resource_id, member_id)
)
return res_member
def get_resource_members(resource_id, res_type):
query = _secure_query(models.ResourceMember).filter_by(
resource_type=res_type
)
# Both resource owner and resource member can do query.
res_members = query.filter(
sa.or_(
_get_criterion(resource_id),
_get_criterion(resource_id, is_owner=False),
)
).all()
return res_members
@b.session_aware()
def update_resource_member(resource_id, res_type, member_id, values,
session=None):
# Only member who is not the owner of the resource can update the
# membership status.
if member_id != security.get_project_id():
raise exc.NotFoundException(
"Resource member not found [resource_id=%s, member_id=%s]" %
(resource_id, member_id)
)
query = _secure_query(models.ResourceMember).filter_by(
resource_type=res_type
)
res_member = query.filter(
_get_criterion(resource_id, member_id, is_owner=False)
).first()
if not res_member:
raise exc.NotFoundException(
"Resource member not found [resource_id=%s, member_id=%s]" %
(resource_id, member_id)
)
res_member.update(values.copy())
return res_member
@b.session_aware()
def delete_resource_member(resource_id, res_type, member_id, session=None):
query = _secure_query(models.ResourceMember).filter_by(
resource_type=res_type
)
res_member = query.filter(_get_criterion(resource_id, member_id)).first()
if not res_member:
raise exc.NotFoundException(
"Resource member not found [resource_id=%s, member_id=%s]" %
(resource_id, member_id)
)
# TODO(lane): Check association with cron triggers when deleting a workflow
# member which is in 'accepted' status.
session.delete(res_member)
@b.session_aware()
def delete_resource_members(**kwargs):
return _delete_all(models.ResourceMember, **kwargs)

View File

@ -249,7 +249,8 @@ class DbTestCase(BaseTest):
db_api_v2.delete_workbooks()
db_api_v2.delete_cron_triggers()
db_api_v2.delete_workflow_definitions()
db_api_v2.delete_environments()
db_api_v2.delete_environments(),
db_api_v2.delete_resource_members()
sqlite_lock.cleanup()

View File

@ -28,6 +28,8 @@ from mistral.services import security
from mistral.tests.unit import base as test_base
user_context = test_base.get_context(default=False)
WORKBOOKS = [
{
'name': 'my_workbook1',
@ -1275,6 +1277,20 @@ class CronTriggerTest(SQLAlchemyTest):
self.assertEqual(created0, fetched[0])
self.assertEqual(created1, fetched[1])
def test_get_cron_triggers_other_tenant(self):
created0 = db_api.create_cron_trigger(CRON_TRIGGERS[0])
# Switch to another tenant.
auth_context.set_ctx(user_context)
fetched = db_api.get_cron_triggers(
pattern='* * * * *',
project_id=security.DEFAULT_PROJECT_ID
)
self.assertEqual(1, len(fetched))
self.assertEqual(created0, fetched[0])
def test_delete_cron_trigger(self):
created = db_api.create_cron_trigger(CRON_TRIGGERS[0])
@ -1571,3 +1587,174 @@ class TXTest(SQLAlchemyTest):
self.assertEqual(created_wb, fetched_wb)
self.assertFalse(self.is_db_session_open())
RESOURCE_MEMBERS = [
{
'resource_id': '123e4567-e89b-12d3-a456-426655440000',
'resource_type': 'workflow',
'project_id': security.get_project_id(),
'member_id': user_context.project_id,
'status': 'pending',
},
{
'resource_id': '123e4567-e89b-12d3-a456-426655440000',
'resource_type': 'workflow',
'project_id': security.get_project_id(),
'member_id': '111',
'status': 'pending',
},
]
class ResourceMemberTest(SQLAlchemyTest):
def test_create_and_get_resource_member(self):
created_1 = db_api.create_resource_member(RESOURCE_MEMBERS[0])
created_2 = db_api.create_resource_member(RESOURCE_MEMBERS[1])
fetched = db_api.get_resource_member(
'123e4567-e89b-12d3-a456-426655440000',
'workflow',
user_context.project_id
)
self.assertEqual(created_1, fetched)
# Switch to another tenant.
auth_context.set_ctx(user_context)
fetched = db_api.get_resource_member(
'123e4567-e89b-12d3-a456-426655440000',
'workflow',
user_context.project_id
)
self.assertEqual(created_1, fetched)
# Tenant A can not see membership of resource shared to Tenant B.
self.assertRaises(
exc.NotFoundException,
db_api.get_resource_member,
'123e4567-e89b-12d3-a456-426655440000',
'workflow',
created_2.member_id
)
def test_create_resource_member_duplicate(self):
db_api.create_resource_member(RESOURCE_MEMBERS[0])
self.assertRaises(
exc.DBDuplicateEntryException,
db_api.create_resource_member,
RESOURCE_MEMBERS[0]
)
def test_get_resource_members_by_owner(self):
for res_member in RESOURCE_MEMBERS:
db_api.create_resource_member(res_member)
fetched = db_api.get_resource_members(
'123e4567-e89b-12d3-a456-426655440000',
'workflow',
)
self.assertTrue(2, len(fetched))
def test_get_resource_members_not_owner(self):
created = db_api.create_resource_member(RESOURCE_MEMBERS[0])
db_api.create_resource_member(RESOURCE_MEMBERS[1])
# Switch to another tenant.
auth_context.set_ctx(user_context)
fetched = db_api.get_resource_members(
created.resource_id,
'workflow',
)
self.assertTrue(1, len(fetched))
self.assertEqual(created, fetched[0])
def test_update_resource_member_by_member(self):
created = db_api.create_resource_member(RESOURCE_MEMBERS[0])
# Switch to another tenant.
auth_context.set_ctx(user_context)
updated = db_api.update_resource_member(
created.resource_id,
'workflow',
user_context.project_id,
{'status': 'accepted'}
)
self.assertEqual(created.id, updated.id)
self.assertEqual('accepted', updated.status)
def test_update_resource_member_by_owner(self):
created = db_api.create_resource_member(RESOURCE_MEMBERS[0])
self.assertRaises(
exc.NotFoundException,
db_api.update_resource_member,
created.resource_id,
'workflow',
user_context.project_id,
{'status': 'accepted'}
)
def test_delete_resource_member(self):
created = db_api.create_resource_member(RESOURCE_MEMBERS[0])
db_api.delete_resource_member(
created.resource_id,
'workflow',
user_context.project_id,
)
fetched = db_api.get_resource_members(
created.resource_id,
'workflow',
)
self.assertEqual(0, len(fetched))
def test_delete_resource_member_not_owner(self):
created = db_api.create_resource_member(RESOURCE_MEMBERS[0])
# Switch to another tenant.
auth_context.set_ctx(user_context)
self.assertRaises(
exc.NotFoundException,
db_api.delete_resource_member,
created.resource_id,
'workflow',
user_context.project_id,
)
def test_delete_resource_member_already_deleted(self):
created = db_api.create_resource_member(RESOURCE_MEMBERS[0])
db_api.delete_resource_member(
created.resource_id,
'workflow',
user_context.project_id,
)
self.assertRaises(
exc.NotFoundException,
db_api.delete_resource_member,
created.resource_id,
'workflow',
user_context.project_id,
)
def test_delete_nonexistent_resource_member(self):
self.assertRaises(
exc.NotFoundException,
db_api.delete_resource_member,
'nonexitent_resource',
'workflow',
'nonexitent_member',
)