-
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
Merged
Merged
Changes from all commits
Commits
Show all changes
20 commits
Select commit
Hold shift + click to select a range
3170dae
Split APIs and types between S3-shares and shares-base
dlpzx 05fd497
Merge remote-tracking branch 'refs/remotes/origin/main' into feat/gen…
dlpzx 17070cf
Fix issues with tables select worksheet and with unused items in sear…
dlpzx 274cbee
Make resource lock generic pt 1
noah-paige e61bebe
Create and delete resource locks on env group or CR create or delete
noah-paige d1ced2a
Add migration script and debug
noah-paige 0c5c0cb
Merge latest from os
noah-paige 07a2e3d
add back resources
noah-paige d1ce3ba
Fix revision IDs
noah-paige 670e12a
Merge branch 'os-main' into generic-resource-locking
noah-paige 3fe45e0
merge latest from main and update alembic revision ID
noah-paige 53ae0a1
Merge latest from main + resolve conflicts + update db migration revi…
noah-paige 3ff6734
Merge latest from main + resolve conflicts + update db migration revi…
noah-paige 74555b1
Merge branch 'os-main' into generic-resource-locking
noah-paige 2b4909a
Merge branch 'os-main' into generic-resource-locking
noah-paige 8867991
Add context manager to acquire locks with retry and move function to …
noah-paige ccde783
Remove isLocked and create/delete + fix migration script
noah-paige d41e8e4
Fix tests
noah-paige 59752d7
make methods private acquire and release
noah-paige 9e2e569
make methods private acquire and release
noah-paige File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
Empty file.
Empty file.
25 changes: 25 additions & 0 deletions
25
backend/dataall/core/resource_lock/db/resource_lock_models.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
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) | ||
acquiredByUri = Column(String, nullable=True) | ||
acquiredByType = Column(String, nullable=True) | ||
|
||
def __init__( | ||
self, | ||
resourceUri: str, | ||
resourceType: str, | ||
acquiredByUri: Optional[str] = None, | ||
acquiredByType: Optional[str] = None, | ||
): | ||
self.resourceUri = resourceUri | ||
self.resourceType = resourceType | ||
self.acquiredByUri = acquiredByUri | ||
self.acquiredByType = acquiredByType | ||
137 changes: 137 additions & 0 deletions
137
backend/dataall/core/resource_lock/db/resource_lock_repositories.py
petrkalos marked this conversation as resolved.
Show resolved
Hide resolved
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
import logging | ||
|
||
from dataall.core.resource_lock.db.resource_lock_models import ResourceLock | ||
from sqlalchemy import and_, or_ | ||
from sqlalchemy.orm import Session | ||
from time import sleep | ||
from typing import List, Tuple | ||
from contextlib import contextmanager | ||
from dataall.base.db.exceptions import ResourceLockTimeout | ||
|
||
log = logging.getLogger(__name__) | ||
|
||
MAX_RETRIES = 10 | ||
RETRY_INTERVAL = 60 | ||
|
||
|
||
class ResourceLockRepository: | ||
@staticmethod | ||
def _acquire_locks(resources, session, acquired_by_uri, acquired_by_type): | ||
""" | ||
Attempts to acquire/create 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], | ||
) | ||
for resource in resources | ||
] | ||
|
||
if not session.query(ResourceLock).filter(or_(*filter_conditions)).first(): | ||
records = [] | ||
for resource in resources: | ||
records.append( | ||
ResourceLock( | ||
resourceUri=resource[0], | ||
resourceType=resource[1], | ||
acquiredByUri=acquired_by_uri, | ||
acquiredByType=acquired_by_type, | ||
) | ||
) | ||
session.add_all(records) | ||
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/delete 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.acquiredByUri == share_uri, | ||
) | ||
) | ||
.with_for_update() | ||
.first() | ||
) | ||
|
||
if resource_lock: | ||
session.delete(resource_lock) | ||
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 | ||
|
||
@staticmethod | ||
@contextmanager | ||
def acquire_lock_with_retry( | ||
resources: List[Tuple[str, str]], session: Session, acquired_by_uri: str, acquired_by_type: str | ||
): | ||
retries_remaining = MAX_RETRIES | ||
log.info(f'Attempting to acquire lock for resources {resources} by share {acquired_by_uri}...') | ||
while not ( | ||
lock_acquired := ResourceLockRepository._acquire_locks( | ||
resources, session, acquired_by_uri, acquired_by_type | ||
) | ||
): | ||
log.info( | ||
f'Lock for one or more resources {resources} already acquired. Retrying in {RETRY_INTERVAL} seconds...' | ||
) | ||
sleep(RETRY_INTERVAL) | ||
retries_remaining -= 1 | ||
if retries_remaining <= 0: | ||
raise ResourceLockTimeout( | ||
'process shares', | ||
f'Failed to acquire lock for one or more of {resources=}', | ||
) | ||
try: | ||
yield lock_acquired | ||
finally: | ||
for resource in resources: | ||
ResourceLockRepository._release_lock(session, resource[0], resource[1], acquired_by_uri) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
could this be a
@dataclass
and avoid the boilerplate ctor?