Skip to content

Commit

Permalink
Ensure settings are retrieved for the right level (#1054)
Browse files Browse the repository at this point in the history
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
  • Loading branch information
soapy1 and pre-commit-ci[bot] authored Jan 27, 2025
1 parent ff757cd commit f6c3ed4
Show file tree
Hide file tree
Showing 4 changed files with 66 additions and 58 deletions.
16 changes: 11 additions & 5 deletions conda-store-server/conda_store_server/_internal/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,11 @@ def get_settings(
self, namespace: str | None = None, environment_name: str | None = None
) -> schema.Settings:
"""Get full schema.settings object for a given level of specificity.
If no namespace or environment is given, then the default settings
are returned. Settings merged follow the merge rules for updating
Settings will be merged together from most specific taking precedence
over least specific. So, if no namespace or environment is given, then
the default settings are returned.
Settings merged follow the merge rules for updating
dict's. So, lists and dict fields are overwritten opposed to merged.
Parameters
Expand Down Expand Up @@ -139,9 +142,10 @@ def get_setting(
namespace: str | None = None,
environment_name: str | None = None,
) -> Any: # noqa: ANN401
"""Get a given setting at the given level of specificity. Will short
cut and look up global setting directly even if a namespace/environment
is specified
"""Get a given setting at the given level of specificity. Settings
will be merged together from most specific taking precedence over
least specific. Will short cut and look up global setting directly
(where appropriate) even if a namespace/environment is specified.
Parameters
----------
Expand All @@ -162,6 +166,8 @@ def get_setting(
return None

prefixes = ["setting"]
# if the setting is a global setting, then there is no need
# to build up a list of prefixes to check
if field.json_schema_extra["metadata"]["global"] is False:
if namespace is not None:
prefixes.append(f"setting/{namespace}")
Expand Down
94 changes: 47 additions & 47 deletions conda-store-server/conda_store_server/_internal/worker/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,10 +82,10 @@ def task_initialize_worker(self):
@shared_task(base=WorkerTask, name="task_watch_paths", bind=True)
def task_watch_paths(self):
conda_store = self.worker.conda_store
# filesystem_namespace is a global setting, don't need to specify namespace/environment
filesystem_namespace = conda_store.get_setting("filesystem_namespace")

with conda_store.session_factory() as db:
settings = conda_store.get_settings()

conda_store.configuration(db).update_storage_metrics(
db, conda_store.config.store_directory
)
Expand All @@ -96,7 +96,7 @@ def task_watch_paths(self):
conda_store.register_environment(
db,
specification=yaml.safe_load(f),
namespace=settings.filesystem_namespace,
namespace=filesystem_namespace,
force=False,
)

Expand Down Expand Up @@ -153,57 +153,57 @@ def task_update_conda_channels(self):
@shared_task(base=WorkerTask, name="task_update_conda_channel", bind=True)
def task_update_conda_channel(self, channel_name):
conda_store = self.worker.conda_store
with conda_store.session_factory() as db:
settings = conda_store.get_settings()

# sanitize the channel name as it's an URL, and it's used for the lock.
sanitizing = {
"https": "",
"http": "",
":": "",
"/": "_",
"?": "",
"&": "_",
"=": "_",
}
channel_name_sanitized = channel_name
for k, v in sanitizing.items():
channel_name_sanitized = channel_name_sanitized.replace(k, v)

task_key = f"lock_{self.name}_{channel_name_sanitized}"

is_locked = False

if conda_store.config.redis_url is not None:
lock = conda_store.redis.lock(task_key, timeout=60 * 15) # timeout 15min
else:
lockfile_path = os.path.join(f"/tmp/task_lock_{task_key}")
lock = FileLock(lockfile_path, timeout=60 * 15)
settings = conda_store.get_settings()

# sanitize the channel name as it's an URL, and it's used for the lock.
sanitizing = {
"https": "",
"http": "",
":": "",
"/": "_",
"?": "",
"&": "_",
"=": "_",
}
channel_name_sanitized = channel_name
for k, v in sanitizing.items():
channel_name_sanitized = channel_name_sanitized.replace(k, v)

task_key = f"lock_{self.name}_{channel_name_sanitized}"

is_locked = False

if conda_store.config.redis_url is not None:
lock = conda_store.redis.lock(task_key, timeout=60 * 15) # timeout 15min
else:
lockfile_path = os.path.join(f"/tmp/task_lock_{task_key}")
lock = FileLock(lockfile_path, timeout=60 * 15)

try:
is_locked = lock.acquire(blocking=False)
try:
is_locked = lock.acquire(blocking=False)

if is_locked:
if is_locked:
with conda_store.session_factory() as db:
channel = api.get_conda_channel(db, channel_name)

conda_store.log.debug(f"updating packages for channel {channel.name}")
channel.update_packages(db, subdirs=settings.conda_platforms)

else:
conda_store.log.debug(
f"skipping updating packages for channel {channel_name} - already in progress"
)

except TimeoutError:
if conda_store.config.redis_url is None:
conda_store.log.warning(
f"Timeout when acquiring lock with key {task_key} - We assume the task is already being run"
)
is_locked = False

finally:
if is_locked:
lock.release()
else:
conda_store.log.debug(
f"skipping updating packages for channel {channel_name} - already in progress"
)

except TimeoutError:
if conda_store.config.redis_url is None:
conda_store.log.warning(
f"Timeout when acquiring lock with key {task_key} - We assume the task is already being run"
)
is_locked = False

finally:
if is_locked:
lock.release()


@shared_task(base=WorkerTask, name="task_solve_conda_environment", bind=True)
Expand Down
9 changes: 5 additions & 4 deletions conda-store-server/conda_store_server/conda_store.py
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,10 @@ def ensure_directories(self):
os.makedirs(self.config.store_directory, exist_ok=True)

def ensure_conda_channels(self, db: Session):
"""Ensure that conda-store indexed channels and packages are in database"""
"""Ensure that conda-store indexed channels and packages are
in database. Only globally defined `conda_indexed_channels`
with the globally defined `conda_channel_alias` will be indexed
"""
self.log.info("updating conda store channels")

settings = self.get_settings()
Expand Down Expand Up @@ -246,9 +249,7 @@ def register_environment(
is_lockfile: bool = False,
):
"""Register a given specification to conda store with given namespace/name."""
settings = self.get_settings()

namespace = namespace or settings.default_namespace
namespace = namespace or self.get_setting("default_namespace")
namespace = api.ensure_namespace(db, name=namespace)

self.config.validate_action(
Expand Down
5 changes: 3 additions & 2 deletions conda-store-server/conda_store_server/conda_store_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,14 @@ def conda_store_validate_action(
namespace: str,
action: auth_schema.Permissions,
) -> None:
settings = conda_store.get_settings()
storage_threshold = conda_store.get_setting("storage_threshold")

system_metrics = api.get_system_metrics(db)

if action in (
auth_schema.Permissions.ENVIRONMENT_CREATE,
auth_schema.Permissions.ENVIRONMENT_UPDATE,
) and (settings.storage_threshold > system_metrics.disk_free):
) and (storage_threshold > system_metrics.disk_free):
raise utils.CondaStoreError(
f"`CondaStore.storage_threshold` reached. Action {action.value} prevented due to insufficient storage space"
)
Expand Down

0 comments on commit f6c3ed4

Please sign in to comment.