"""Compose the HPC offering-account pass from what the process is configured with (#765). The pass is built only when the installation names a Waldur API, a token for the HPC offering and the Site whose usernames those accounts carry. Without all four settings nothing is built and no worker runs, which is what an installation without an HPC offering looks like; that installation logs `disabled` rather than looking configured. Half the settings is a configuration error, logged with the names that are missing and refused at start-up, so a pass can never run against a second Site's bindings or a token that is not there. `WALDUR_HPC_SITE` is a Site *environment* (`k8s`, `slurm`), the key the `site` binding is stored under, not a Site ID such as `icarus-hpc`: the binding a member's username lives on is held per environment, and the accounts are reported to that environment's intake. The token is read through the same secret loader as every other credential, so it is never printed and `_FILE` works. """ import logging import json import os from datetime import UTC, datetime from metallo.adaptors.party_project_views import PartyProjectReads from metallo.adaptors.postgres import Database from metallo.contexts.resource_provisioning.adaptors.native_accounts import NativeAccountStore from metallo.contexts.resource_provisioning.adaptors.postgres import resource_provisioning, saved_allocations from metallo.contexts.resource_provisioning.adaptors.view_member_accounts import ViewMemberAccounts from metallo.contexts.resource_provisioning.adaptors.view_projects import ViewProjectBindings from metallo.contexts.resource_provisioning.adaptors.waldur_offering_resources import WaldurOfferingResources from metallo.contexts.resource_provisioning.adaptors.waldur_offering_users import WaldurOfferingUsers from metallo.contexts.resource_provisioning.adaptors.waldur_resource_accounts import WaldurResourceAccounts from metallo.contexts.resource_provisioning.application.accounts import NativeAccounts from metallo.contexts.resource_provisioning.application.hpc_accounts import HpcOfferingAccounts from metallo.contexts.resource_provisioning.domain.values import ENVIRONMENTS, limits, opaque from metallo.foundation.domain import Rejection from metallo.foundation.secrets import optional_secret LOG = logging.getLogger("pharos.metallo.hpc_accounts") API_URL = "WALDUR_API_URL" TOKEN = "WALDUR_HPC_OFFERING_TOKEN" OFFERING = "WALDUR_HPC_OFFERING_UUID" SITE = "WALDUR_HPC_SITE" LIMITS = "WALDUR_HPC_ALLOCATION_LIMITS" def allocation_reports(database: Database, api_url: str, token: str, offering: str, site: str) -> WaldurOfferingResources | None: declared = optional_secret(LIMITS) if declared is None: return None try: configured = limits(json.loads(declared)) if not configured.get("cpu") or not configured.get("memory"): raise ValueError("CPU and memory are required") instance = opaque(os.environ.get("RESOURCE_PROVISIONING_WALDUR_INSTANCE", ""), "The provider instance") except (ValueError, TypeError, Rejection): raise RuntimeError("HPC allocation reports need valid WALDUR_HPC_ALLOCATION_LIMITS with positive cpu and " "memory, and RESOURCE_PROVISIONING_WALDUR_INSTANCE as Beto names the installation") from None projects = ViewProjectBindings(database) return WaldurOfferingResources(api_url, lambda: token, offering, instance, site, configured, resource_provisioning(database, projects), lambda: datetime.now(UTC)) def hpc_offering_accounts(database: Database, view: PartyProjectReads | None) -> HpcOfferingAccounts | None: """The pass, or None when this installation does not name an HPC offering.""" settings = {name: optional_secret(name) for name in (API_URL, TOKEN, OFFERING, SITE)} absent = sorted(name for name, value in settings.items() if value is None) if len(absent) == len(settings): LOG.info("HPC offering-account pass disabled: none of %s is set", ", ".join(sorted(settings))) return None if absent: LOG.warning("HPC offering-account pass disabled: %s not set", ", ".join(absent)) raise RuntimeError("The HPC account pass is missing " + ", ".join(absent)) api_url, token, offering, site = (settings[API_URL], settings[TOKEN], settings[OFFERING], settings[SITE]) assert api_url is not None and token is not None and offering is not None and site is not None if site not in ENVIRONMENTS: LOG.warning("HPC offering-account pass disabled: WALDUR_HPC_SITE is not a Site environment") raise RuntimeError(f"WALDUR_HPC_SITE must be one of {', '.join(ENVIRONMENTS)}, the Site's environment" " its `site` binding is stored under, not a Site ID") accounts = NativeAccounts(saved_allocations(database), NativeAccountStore(database)) return HpcOfferingAccounts( users=WaldurOfferingUsers(api_url, lambda: token, offering), members=ViewMemberAccounts(database, view), allocations=saved_allocations(database), accounts=accounts, site=site, clock=lambda: datetime.now(UTC), offering_uuid=offering, allocation_reports=allocation_reports(database, api_url, token, offering, site), resource_accounts=WaldurResourceAccounts(api_url, lambda: token, offering), )