Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions .github/workflows/docker_publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,7 @@ on:


env:
# Use docker.io for Docker Hub if empty
REGISTRY: ghcr.io
# github.repository as <account>/<repo>
IMAGE_NAME: ${{ github.repository }}


Expand Down Expand Up @@ -56,7 +54,7 @@ jobs:
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0

# Login against a Docker registry
# Login against registry
# https://github.com/docker/login-action
- name: Log into registry ${{ env.REGISTRY }}
uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0
Expand All @@ -65,7 +63,7 @@ jobs:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

# Extract metadata (tags, labels) for Docker
# Extract metadata (tags, labels)
# https://github.com/docker/metadata-action
- name: Extract Docker metadata
id: meta
Expand Down
6 changes: 6 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
FROM python:3.12-slim-trixie
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/

# Used to associate the image with a source repository outside GHA
LABEL org.opencontainers.image.source=https://github.com/slaclab/coact-api

# This avoids needing `uv run` or venv activation at runtime
ENV UV_PROJECT_ENVIRONMENT=/usr/local

RUN mkdir -p /app
WORKDIR /app

Expand Down
1 change: 1 addition & 0 deletions models.py
Original file line number Diff line number Diff line change
Expand Up @@ -490,6 +490,7 @@ class FacillityPastXUsage:
clustername: Optional[str] = UNSET
resourceHours: Optional[float] = 0
percentUsed: float
purchasedNodes: Optional[int] = UNSET

@strawberry.type
class RepoPastXUsage:
Expand Down
5 changes: 4 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,12 @@ dependencies = [
"websockets==10.4",
"gql[requests]>=3.4.1",
"httpx>=0.24.0",
"coact-client @ file://${PROJECT_ROOT}/client",
"coact-client",
]

[tool.uv.sources]
coact-client = { path = "client", editable = true }


[dependency-groups]
dev = [
Expand Down
11 changes: 7 additions & 4 deletions schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -317,15 +317,18 @@ def facilityRecentComputeUsage(self, info: Info, past_minutes: int, skipQoses: L
purs = list(info.context.db.collection("facility_compute_purchases").aggregate([
{ "$lookup": { "from": "clusters", "localField": "clustername", "foreignField": "name", "as": "cluster"}},
{ "$unwind": "$cluster" },
{ "$project": { "_id": 0, "facility": "$facility", "clustername": "$clustername", "purchasedNodes": { "$multiply": [ "$servers", "$cluster.nodecpucount" ] } }},
{ "$project": { "_id": 0, "facility": "$facility", "clustername": "$clustername", "purchasedNodes": { "$toInt": { "$multiply": [ "$servers", "$cluster.nodecpucount" ] } } }},
]))
fac2prs = { (x["facility"], x["clustername"]) : x["purchasedNodes"]*(past_minutes/60.0) for x in purs }
fac2prs = { (x["facility"], x["clustername"]) : (x["purchasedNodes"] or 0)*(past_minutes/60.0) for x in purs }
fac2nodes = { (x["facility"], x["clustername"]) : (x["purchasedNodes"] or 0) for x in purs }
for usg in aggs:
adj = fac2prs.get((usg["facility"], usg["clustername"]), 0)
if adj:
usg["percentUsed"] = (usg["resourceHours"]/adj)*100.0
# Add purchased nodes to the aggregation data
usg["purchasedNodes"] = fac2nodes.get((usg["facility"], usg["clustername"]), 0)

return [ FacillityPastXUsage(**{k:x.get(k, 0) for k in ["facility", "clustername", "resourceHours", "percentUsed" ] }) for x in aggs ]
return [ FacillityPastXUsage(**{k:x.get(k, 0) for k in ["facility", "clustername", "resourceHours", "percentUsed", "purchasedNodes" ] }) for x in aggs ]

@strawberry.field( permission_classes=[ IsAuthenticated ] )
def access_groups(self, info: Info, filter: Optional[AccessGroupInput]={} ) -> List[AccessGroup]:
Expand Down Expand Up @@ -628,7 +631,7 @@ def repoRecentComputeUsage(self, info: Info, past_minutes: int, skipQoses: List[
{ "$unwind": "$cluster" },
{ "$project": { "_id": 0, "facility": "$facility", "clustername": "$clustername", "purchasedNodes": { "$multiply": [ "$servers", "$cluster.nodecpucount" ] } }},
]))
fac2prs = { (x["facility"], x["clustername"]) : x["purchasedNodes"]*(past_minutes/60.0) for x in purs}
fac2prs = { (x["facility"], x["clustername"]) : (x["purchasedNodes"] or 0)*(past_minutes/60.0) for x in purs}
for usg in aggs:
adj = fac2prs.get((usg["facility"], usg["clustername"]), 0)
if adj:
Expand Down
33 changes: 33 additions & 0 deletions tests/integration/test_purchased_nodes_integration.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
"""
Integration tests for purchased nodes functionality - requires full stack.

Tests that facilityRecentComputeUsage returns the purchasedNodes field.
Requires: docker compose up (API + MongoDB running)
"""
from coact.client import CoactClient


async def test_facility_recent_compute_usage_purchased_nodes(client: CoactClient):
"""Test that facilityRecentComputeUsage returns purchasedNodes as an integer field."""
response = await client.execute(
query="""
query {
facilityRecentComputeUsage(pastMinutes: 60) {
facility
clustername
resourceHours
percentUsed
purchasedNodes
}
}
"""
)
data = client.get_data(response)
results = data["facilityRecentComputeUsage"]

assert isinstance(results, list)

for record in results:
assert "purchasedNodes" in record
if record["purchasedNodes"] is not None:
assert isinstance(record["purchasedNodes"], int)
4 changes: 2 additions & 2 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.