-
Notifications
You must be signed in to change notification settings - Fork 82
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Convert Dataset Lock Mechanism to Generic Resource Lock #1338
Changes from 13 commits
3170dae
05fd497
17070cf
274cbee
e61bebe
d1ced2a
0c5c0cb
07a2e3d
d1ce3ba
670e12a
3fe45e0
53ae0a1
3ff6734
74555b1
2b4909a
8867991
ccde783
d41e8e4
59752d7
9e2e569
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,11 +1,3 @@ | ||
"""The package contains the core functionality that is required by data.all to work correctly""" | ||
|
||
from dataall.core import ( | ||
permissions, | ||
stacks, | ||
groups, | ||
environment, | ||
organizations, | ||
tasks, | ||
vpc, | ||
) | ||
from dataall.core import permissions, stacks, groups, environment, organizations, tasks, vpc, resource_lock |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
from typing import Optional | ||
from sqlalchemy import Column, String, Boolean | ||
|
||
from dataall.base.db import Base | ||
|
||
|
||
class ResourceLock(Base): | ||
__tablename__ = 'resource_lock' | ||
|
||
resourceUri = Column(String, nullable=False, primary_key=True) | ||
resourceType = Column(String, nullable=False, primary_key=True) | ||
isLocked = Column(Boolean, default=False) | ||
acquiredByUri = Column(String, nullable=True) | ||
acquiredByType = Column(String, nullable=True) | ||
|
||
def __init__( | ||
self, | ||
resourceUri: str, | ||
resourceType: str, | ||
isLocked: bool = False, | ||
acquiredByUri: Optional[str] = None, | ||
acquiredByType: Optional[str] = None, | ||
): | ||
self.resourceUri = resourceUri | ||
self.resourceType = resourceType | ||
self.isLocked = isLocked | ||
self.acquiredByUri = acquiredByUri | ||
self.acquiredByType = acquiredByType |
petrkalos marked this conversation as resolved.
Show resolved
Hide resolved
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
import logging | ||
|
||
from dataall.core.resource_lock.db.resource_lock_models import ResourceLock | ||
from sqlalchemy import and_, or_ | ||
|
||
log = logging.getLogger(__name__) | ||
|
||
|
||
class ResourceLockRepository: | ||
@staticmethod | ||
def create_resource_lock( | ||
session, resource_uri, resource_type, is_locked=False, acquired_by_uri=None, acquired_by_type=None | ||
): | ||
resource_lock = ResourceLock( | ||
resourceUri=resource_uri, | ||
resourceType=resource_type, | ||
isLocked=is_locked, | ||
acquiredByUri=acquired_by_uri, | ||
acquiredByType=acquired_by_type, | ||
) | ||
session.add(resource_lock) | ||
session.commit() | ||
|
||
@staticmethod | ||
def delete_resource_lock(session, resource_uri): | ||
resource_lock = session.query(ResourceLock).filter(ResourceLock.resourceUri == resource_uri).first() | ||
session.delete(resource_lock) | ||
session.commit() | ||
|
||
@staticmethod | ||
def acquire_locks(resources, session, acquired_by_uri, acquired_by_type): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. should this be a ContextManager? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I changed logic to make Let me know if this is what you were thinking as well There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think |
||
""" | ||
Attempts to acquire one or more locks on the resources identified by resourceUri and resourceType. | ||
|
||
Args: | ||
resources: List of resource tuples (resourceUri, resourceType) to acquire locks for. | ||
session (sqlalchemy.orm.Session): The SQLAlchemy session object used for interacting with the database. | ||
acquired_by_uri: The ID of the resource that is attempting to acquire the lock. | ||
acquired_by_type: The resource type that is attempting to acquire the lock.qu | ||
|
||
Returns: | ||
bool: True if the lock is successfully acquired, False otherwise. | ||
""" | ||
try: | ||
# Execute the query to get the ResourceLock object | ||
filter_conditions = [ | ||
and_( | ||
ResourceLock.resourceUri == resource[0], | ||
ResourceLock.resourceType == resource[1], | ||
~ResourceLock.isLocked, | ||
) | ||
for resource in resources | ||
] | ||
resource_locks = session.query(ResourceLock).filter(or_(*filter_conditions)).with_for_update().all() | ||
|
||
# Ensure lock record found for each resource | ||
if len(resource_locks) == len(resources): | ||
# Update the attributes of the ResourceLock object | ||
for resource_lock in resource_locks: | ||
resource_lock.isLocked = True | ||
resource_lock.acquiredByUri = acquired_by_uri | ||
resource_lock.acquiredByType = acquired_by_type | ||
session.commit() | ||
return True | ||
else: | ||
log.info( | ||
'Not all ResourceLocks were found. One or more ResourceLocks may be acquired by another resource...' | ||
) | ||
return False | ||
except Exception as e: | ||
session.expunge_all() | ||
session.rollback() | ||
log.error('Error occurred while acquiring lock:', e) | ||
return False | ||
|
||
@staticmethod | ||
def release_lock(session, resource_uri, resource_type, share_uri): | ||
""" | ||
Releases the lock on the resource identified by resource_uri, resource_type. | ||
|
||
Args: | ||
session (sqlalchemy.orm.Session): The SQLAlchemy session object used for interacting with the database. | ||
resource_uri: The ID of the resource that owns the lock. | ||
resource_type: The type of the resource that owns the lock. | ||
share_uri: The ID of the share that is attempting to release the lock. | ||
|
||
Returns: | ||
bool: True if the lock is successfully released, False otherwise. | ||
""" | ||
try: | ||
log.info(f'Releasing lock for resource: {resource_uri=}, {resource_type=}') | ||
|
||
resource_lock = ( | ||
session.query(ResourceLock) | ||
.filter( | ||
and_( | ||
ResourceLock.resourceUri == resource_uri, | ||
ResourceLock.resourceType == resource_type, | ||
ResourceLock.isLocked, | ||
ResourceLock.acquiredByUri == share_uri, | ||
) | ||
) | ||
.with_for_update() | ||
.first() | ||
) | ||
|
||
if resource_lock: | ||
resource_lock.isLocked = False | ||
resource_lock.acquiredByUri = '' | ||
resource_lock.acquiredByType = '' | ||
|
||
session.commit() | ||
return True | ||
else: | ||
log.info(f'ResourceLock not found for resource: {resource_uri=}, {resource_type=}') | ||
return False | ||
|
||
except Exception as e: | ||
session.expunge_all() | ||
session.rollback() | ||
log.error('Error occurred while releasing lock:', e) | ||
return False |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
don't we assume that groupUris are unique? why do you need to append the envUri?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The same group can be a memeber of many different environments
In practice primary key for
environment_group_permission
(aka EnvironmentGroup) is groupUri + environmentUriWe similarly need a way to distinguish each Env + Group Pair for resource lock since the underlying resource we are "locking" is the consumer IAM role for the group in that particular env