openapi: 3.1.0 info: title: Resource Provisioning reports version: 1.0.0 description: >- Owned by Resource Provisioning (working name, #763); consumed by the Waldur Site Agent of each execution Site. After each cycle the agent reports every resource of its offering as it last observed it: Waldur's state, whether it is paused, the limits ordered and the limits the Site applied. The agent never sends a canonical identity: the allocation is derived from the resource, and the Project is resolved from Beto's Project bindings. The Site is bound to the credential, not to the message: each Site's path prefix is guarded by its own key, so an agent cannot report for another Site. Reports for one resource are applied in `observedAt` order; an older one is refused as stale, and one that changes nothing renews the allocation's freshness without a new revision. Limits are canonical integers: `cpu` in millicores, `memory`, `storage` and `ephemeral-storage` in bytes, and accelerators `gpu.` in whole devices. The agent translates its provider units without rounding. security: - siteBearer: [] paths: /resource-provisioning/{site}/reports: post: operationId: report summary: Apply one resource report. Repeating a request with the same content returns the saved result. parameters: - {name: site, in: path, required: true, schema: {$ref: '#/components/schemas/Site'}} requestBody: required: true content: application/json: schema: {$ref: '#/components/schemas/ReportRequest'} responses: '200': description: The report is saved. The result carries the allocation's lifecycle and revision. content: application/json: schema: {$ref: '#/components/schemas/ReportResult'} '422': description: >- The report was refused, for example as stale or because the allocation is retired. The refusal is saved with its request, so the agent chooses a new request ID for its next report. content: application/json: schema: {$ref: '#/components/schemas/ReportResult'} '400': description: The message does not match this contract. Nothing is stored and the input is not echoed. content: application/json: schema: {$ref: '#/components/schemas/Failure'} '401': description: The Site credential is missing or wrong. '503': description: The store or Beto is unavailable. Repeat the same request. content: application/json: schema: {$ref: '#/components/schemas/Failure'} /resource-provisioning/{site}/accounts: post: operationId: reportAccounts summary: Apply one report of the native accounts the agent sees for one resource. Repeating a request with the same content returns the saved result. description: >- The report is the complete set of member accounts the agent found in the resource's native scope. It confirms that an account exists with the template's objects; it never grants an account, never removes one and never decides what a launch may do. An account that is not listed is simply not confirmed. Each account is named by what the agent wrote on it: the Party, the Party's `site` binding and the member generation, never by a username or an email address. parameters: - {name: site, in: path, required: true, schema: {$ref: '#/components/schemas/Site'}} requestBody: required: true content: application/json: schema: {$ref: '#/components/schemas/AccountsRequest'} responses: '200': description: The report is saved. The result says how many of its accounts are `ready`. content: application/json: schema: {$ref: '#/components/schemas/AccountsResult'} '422': description: >- The report was refused as stale (older than the one saved for the resource) or because the resource is unknown at this Site or retired. Nothing is stored; the agent reports again, with a newer `observedAt`. content: application/json: schema: {$ref: '#/components/schemas/AccountsResult'} '400': description: The message does not match this contract. Nothing is stored and the input is not echoed. content: application/json: schema: {$ref: '#/components/schemas/Failure'} '401': description: The Site credential is missing or wrong. '503': description: The store is unavailable. Repeat the same request. content: application/json: schema: {$ref: '#/components/schemas/Failure'} /resource-provisioning/{site}/allocations/{allocationId}: get: operationId: getAllocation summary: The allocation's lifecycle, revision and the reasons it is not ready. parameters: - {name: site, in: path, required: true, schema: {$ref: '#/components/schemas/Site'}} - {name: allocationId, in: path, required: true, schema: {$ref: '#/components/schemas/Id'}} responses: '200': description: The allocation. content: application/json: schema: {$ref: '#/components/schemas/AllocationStatus'} '400': description: The allocation ID is malformed. content: application/json: schema: {$ref: '#/components/schemas/Failure'} '401': description: The Site credential is missing or wrong. '404': description: No such allocation at this Site. content: application/json: schema: {$ref: '#/components/schemas/Failure'} '503': description: The store is unavailable. Repeat the request. content: application/json: schema: {$ref: '#/components/schemas/Failure'} /resource-provisioning/{site}/projects/{providerProjectRef}: get: operationId: getProject summary: The canonical Project behind a provider Project, before the Site has an allocation for it. description: >- The agent labels a new execution namespace with the canonical Project, and the label cannot change later, so it asks before it creates the namespace and before any allocation or report exists. The answer is Beto's binding as this context's view holds it. A Project that is unknown or claimed by two bindings is not found; a closed Project still resolves, as the binding is identity and access is Beto's evidence. parameters: - {name: site, in: path, required: true, schema: {$ref: '#/components/schemas/Site'}} - {name: providerProjectRef, in: path, required: true, schema: {$ref: '#/components/schemas/Opaque'}} responses: '200': description: The Project. content: application/json: schema: {$ref: '#/components/schemas/ProjectResult'} '400': description: A parameter does not match this contract. content: application/json: schema: {$ref: '#/components/schemas/Failure'} '401': description: The Site credential is missing or wrong. '404': description: No such Project. content: application/json: schema: {$ref: '#/components/schemas/Failure'} '503': description: The view or the store is unavailable. Nothing is created; repeat the request. content: application/json: schema: {$ref: '#/components/schemas/Failure'} /resource-provisioning/{site}/members/{username}: get: operationId: getMember summary: The verified member of a provider Project behind a Site username, for the agent's native account. description: >- The agent resolves the offering username Waldur passes it, in the provider Project of the resource, to the canonical Project and Party. Only a Project that has an allocation at this Site is answered, so a Site's key cannot probe other Projects. `state` is `retired` when Beto has retired the Party's `site` binding: the agent withdraws that account at once. The generation advances when the Party is added again after leaving, so the agent never revives an account of an earlier one. parameters: - {name: site, in: path, required: true, schema: {$ref: '#/components/schemas/Site'}} - {name: username, in: path, required: true, schema: {$ref: '#/components/schemas/Opaque'}} - {name: providerProjectRef, in: query, required: true, schema: {$ref: '#/components/schemas/Opaque'}} responses: '200': description: The member. content: application/json: schema: {$ref: '#/components/schemas/MemberResult'} '400': description: A parameter does not match this contract. content: application/json: schema: {$ref: '#/components/schemas/Failure'} '401': description: The Site credential is missing or wrong. '404': description: >- No such member: the Project is unknown, unbound or has no allocation at this Site, the username has no verified Party, or the Party is not a member of the Project. content: application/json: schema: {$ref: '#/components/schemas/Failure'} '503': description: The views or the store are unavailable. Nothing is granted or withdrawn; repeat the request. content: application/json: schema: {$ref: '#/components/schemas/Failure'} /resource-provisioning/{site}/retained-accounts: get: operationId: getRetainedAccounts summary: The member accounts this Site keeps for work its Projects have already accepted, as one complete list. description: >- When Waldur drops a member, the agent withdraws their account unless it is in this list. An account is listed while at least one accepted item for its Party, Project, this Site and account generation is required, and nothing has fenced it: the Project is still active with the management generation the work was accepted under, the Party's `site` binding is still established, and the Party has not been added again with a newer generation. The list is complete, never a page, and `revision` rises with every change to the items of this Site. An agent that cannot read it keeps every account and withdraws none. parameters: - {name: site, in: path, required: true, schema: {$ref: '#/components/schemas/Site'}} responses: '200': description: The accounts to keep. content: application/json: schema: {$ref: '#/components/schemas/RetainedAccounts'} '401': description: The Site credential is missing or wrong. '503': description: The views or the store are unavailable. Nothing is withdrawn; repeat the request. content: application/json: schema: {$ref: '#/components/schemas/Failure'} components: securitySchemes: siteBearer: type: http scheme: bearer description: >- The credential of one Site's agent. It opens only that Site's paths of this contract; no other Metallo route accepts it, and the service's own key does not open these. schemas: Failure: type: object additionalProperties: false required: [code] properties: code: {type: string, pattern: '^[a-z][a-z_]{0,63}$'} Id: {type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$'} Opaque: {type: string, pattern: '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$'} AccountName: {type: string, pattern: '^[A-Za-z0-9_][A-Za-z0-9_.:-]{0,63}$'} Timestamp: {type: string, pattern: '^\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d(?:\.\d+)?(?:Z|[+-]\d\d:\d\d)$'} Site: {type: string, enum: [k8s, slurm]} Revision: {type: integer, minimum: 1} Lifecycle: {type: string, enum: [pending, ready, changing, suspended, retired]} Reason: type: string enum: [project_unbound, provisioning, provider_erred, limits_not_applied, limits_changing, provider_paused] Limits: type: object description: Component to quantity in canonical units; at least one component. minProperties: 1 maxProperties: 32 additionalProperties: {type: integer, minimum: 0, maximum: 9007199254740991} Resource: type: object additionalProperties: false required: [resourceRef, offeringRef, providerProjectRef, providerState, paused, configured, observedAt] properties: resourceRef: description: The Waldur resource's UUID. The allocation is derived from it. $ref: '#/components/schemas/Opaque' offeringRef: description: The Waldur offering's UUID. $ref: '#/components/schemas/Opaque' providerProjectRef: description: The Waldur project's UUID, resolved to the canonical Project through Beto. $ref: '#/components/schemas/Opaque' providerState: description: The Waldur resource's state. type: string enum: [Creating, OK, Updating, Erred, Terminating, Terminated] paused: description: Whether Waldur has paused the resource; a paused allocation is suspended. type: boolean nativeScope: description: The Site's own name for the resource, such as its Kubernetes namespace; absent until it exists. $ref: '#/components/schemas/Opaque' configured: description: The resource's limits as ordered in Waldur. $ref: '#/components/schemas/Limits' applied: description: The limits the Site enforces, as the agent read them back; absent until applied. $ref: '#/components/schemas/Limits' used: description: >- What the Site's quota counts as used when the agent observed it, in the same canonical units; absent when the Site cannot read it. It is not a limit and never changes the allocation's revision (#1074). $ref: '#/components/schemas/Limits' observedAt: description: When the agent observed the resource. $ref: '#/components/schemas/Timestamp' ReportRequest: type: object additionalProperties: false required: [requestId, resource] properties: requestId: {$ref: '#/components/schemas/Opaque'} resource: {$ref: '#/components/schemas/Resource'} Rejection: type: object additionalProperties: false required: [code, message] properties: code: {type: string, pattern: '^[a-z][a-z_]{0,63}$'} message: {type: string, maxLength: 512} ReportResult: type: object additionalProperties: false required: [requestId, allocationId, outcome] properties: requestId: {$ref: '#/components/schemas/Opaque'} allocationId: {$ref: '#/components/schemas/Id'} outcome: {type: string, enum: [accepted, rejected]} lifecycle: {$ref: '#/components/schemas/Lifecycle'} revision: {$ref: '#/components/schemas/Revision'} rejection: {$ref: '#/components/schemas/Rejection'} Account: type: object additionalProperties: false required: [partyId, accountReference, nativeUid, bindingId, generation, state] properties: partyId: {$ref: '#/components/schemas/Id'} accountReference: description: >- The name the Site gave the member's account in the resource's native scope, which Metallo keeps and hands back as the account Site Runtime acts as. A Kubernetes Site names it `member-` and Metallo refuses any other name; a Slurm Site names it with the POSIX login name it made for the member (lowercase letters, digits, dot, underscore or hyphen, at most 32 characters, not starting with a digit or a hyphen), and Metallo refuses a name that is not a login name. The name is the Site's fact: it is never derived from the Party. $ref: '#/components/schemas/AccountName' nativeUid: description: >- The Site's immutable UID of the account as the agent read it back. An account deleted and made again has another one, which the name, binding and generation do not show. type: string minLength: 1 maxLength: 256 pattern: '^\S+$' bindingId: description: Beto's ID of the Party's `site` binding, as the agent wrote it on the account. $ref: '#/components/schemas/Opaque' generation: description: The member generation the account was made for, as the agent wrote it on the account. $ref: '#/components/schemas/Revision' state: description: >- `ready` when the account and all its Roles and bindings exist; `incomplete` when only part of them do, as an interrupted provisioning leaves it. type: string enum: [ready, incomplete] AccountsRequest: type: object additionalProperties: false required: [requestId, resourceRef, observedAt, accounts] properties: requestId: {$ref: '#/components/schemas/Opaque'} resourceRef: description: The Waldur resource's UUID. The allocation is derived from it, as for a report. $ref: '#/components/schemas/Opaque' observedAt: description: When the agent listed the accounts. $ref: '#/components/schemas/Timestamp' accounts: description: Every account the agent found in the resource's native scope; empty when it found none. type: array maxItems: 4096 items: {$ref: '#/components/schemas/Account'} AccountsResult: type: object additionalProperties: false required: [requestId, allocationId, outcome] properties: requestId: {$ref: '#/components/schemas/Opaque'} allocationId: {$ref: '#/components/schemas/Id'} outcome: {type: string, enum: [accepted, rejected]} confirmed: description: How many of the reported accounts are `ready`. type: integer minimum: 0 rejection: {$ref: '#/components/schemas/Rejection'} ProjectResult: type: object additionalProperties: false required: [projectId] properties: projectId: {$ref: '#/components/schemas/Id'} RetainedAccounts: type: object additionalProperties: false required: [revision, accounts] properties: revision: description: Zero while this Site has no accepted item; otherwise it rises with every change to one. type: integer minimum: 0 accounts: type: array maxItems: 10000 items: {$ref: '#/components/schemas/RetainedAccount'} RetainedAccount: type: object additionalProperties: false required: [projectId, partyId, bindingId, generation] properties: projectId: {$ref: '#/components/schemas/Id'} partyId: {$ref: '#/components/schemas/Id'} bindingId: description: Beto's ID of the Party's `site` binding; the agent's binding is this joined to the generation. $ref: '#/components/schemas/Opaque' generation: {$ref: '#/components/schemas/Revision'} MemberResult: type: object additionalProperties: false required: [projectId, partyId, bindingId, bindingRevision, generation, state] properties: projectId: {$ref: '#/components/schemas/Id'} partyId: {$ref: '#/components/schemas/Id'} bindingId: description: Beto's ID of the Party's `site` binding. $ref: '#/components/schemas/Opaque' bindingRevision: {$ref: '#/components/schemas/Revision'} generation: {$ref: '#/components/schemas/Revision'} state: {type: string, enum: [active, retired]} AllocationStatus: type: object additionalProperties: false required: [allocationId, lifecycle, revision, reasons, observedAt] properties: allocationId: {$ref: '#/components/schemas/Id'} lifecycle: {$ref: '#/components/schemas/Lifecycle'} revision: {$ref: '#/components/schemas/Revision'} reasons: type: array maxItems: 8 items: {$ref: '#/components/schemas/Reason'} observedAt: {$ref: '#/components/schemas/Timestamp'}