Merge "Add db operations for resource members"
This commit is contained in:
commit
cc3e106a06
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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',
|
||||
)
|
||||
|
Loading…
x
Reference in New Issue
Block a user