Skip to content
Open
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
8 changes: 8 additions & 0 deletions demo/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
FROM golang:1.24 AS build
WORKDIR /src
COPY . .
RUN go build -o /out/inventory ./...

FROM gcr.io/distroless/static
COPY --from=build /out/inventory /inventory
ENTRYPOINT ["/inventory"]
5 changes: 3 additions & 2 deletions demo/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ metadata:
spec:
driver: gpu.example.com
parameters:
sharing: none
memory: 16Gi
sharing: time-sliced
Comment thread
danilrwx marked this conversation as resolved.
memory: 24Gi
replicas: 4
nodeSelector:
accelerator: nvidia
5 changes: 3 additions & 2 deletions demo/go.sum
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
example.com/dra v1.3.0 h1:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=
example.com/dra v1.3.0/go.mod h1:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb=
example.com/dra v1.4.0 h1:eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee=
example.com/dra v1.4.0/go.mod h1:fffffffffffffffffffffffffffffffffffffffffff=
k8s.io/api v0.30.0 h1:ccccccccccccccccccccccccccccccccccccccccccc=
k8s.io/api v0.30.0/go.mod h1:ddddddddddddddddddddddddddddddddddddddddddd=
k8s.io/klog/v2 v2.120.1 h1:ggggggggggggggggggggggggggggggggggggggggggg=
15 changes: 14 additions & 1 deletion demo/gpu.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,19 @@ package gpu

import (
"context"
"errors"
"fmt"
)

// ErrNodeNotFound is returned when a node has no inventory yet.
var ErrNodeNotFound = errors.New("node not found")

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call extracting a sentinel error — now callers can errors.Is(err, ErrNodeNotFound) instead of string-matching.


// DeviceInfo describes a single GPU exposed to a pod.
type DeviceInfo struct {
Name string
Driver string
UUID string
Shared bool
}

// StatusController reconciles GPU claims against node inventory.
Expand All @@ -21,16 +26,24 @@ type StatusController struct {
func (c *StatusController) getGPUStatus(info DeviceInfo, node string) (string, error) {
devices, ok := c.nodes[node]
if !ok {
return "", fmt.Errorf("node %q not found", node)
return "", fmt.Errorf("%w: %q", ErrNodeNotFound, node)
}
for _, d := range devices {
if d.UUID == info.UUID {
if d.Shared {
return "shared", nil
}
return "allocated", nil
}
Comment on lines +33 to 37

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This shared-vs-allocated branch reads cleanly. Consider a small table test covering both statuses.

}
return "pending", nil
}

// Count returns how many devices a node exposes.
func (c *StatusController) Count(node string) int {
return len(c.nodes[node])

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should Count guard against a nil map, or is the zero value fine here?

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Zero value is fine — len(nil map) is 0 in Go, so no guard needed.

}

func (c *StatusController) Reconcile(ctx context.Context, node string) error {
_, err := c.getGPUStatus(DeviceInfo{}, node)
return err
Expand Down
4 changes: 4 additions & 0 deletions demo/old_helper.rs → demo/helper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,7 @@ pub fn format_uuid(raw: &str) -> String {
pub fn is_valid(uuid: &str) -> bool {
uuid.len() == 36 && uuid.chars().filter(|c| *c == '-').count() == 4
}

pub fn short(uuid: &str) -> &str {
uuid.split('-').next().unwrap_or(uuid)
}
10 changes: 0 additions & 10 deletions demo/legacy.rb

This file was deleted.

Binary file added demo/logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
16 changes: 16 additions & 0 deletions demo/metrics.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import Foundation

struct GPUMetrics {
let uuid: String
let utilization: Double
let memoryUsed: UInt64

var isSaturated: Bool {
utilization > 0.9

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is 0.9 the right saturation threshold for MIG-sliced GPUs? Might be worth a named constant.

}
}

func summarize(_ samples: [GPUMetrics]) -> String {
let busy = samples.filter { $0.isSaturated }.count
return "\(busy)/\(samples.count) GPUs saturated"
}
5 changes: 5 additions & 0 deletions demo/queries
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
SELECT node, COUNT(*) AS gpus, SUM(memory_gb) AS total_memory
FROM inventory
WHERE status = 'ready'
GROUP BY node
ORDER BY total_memory DESC;
8 changes: 7 additions & 1 deletion demo/server.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
"""A tiny async status server for the DRA controller."""
import asyncio
from dataclasses import dataclass
from dataclasses import dataclass, field


@dataclass
class Claim:
name: str
driver: str
ready: bool = False
devices: list[str] = field(default_factory=list)

@danilrwx danilrwx Jul 30, 2026

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Prefer an immutable default so the dataclass instances don't share state:

Suggested change
devices: list[str] = field(default_factory=list)
devices: tuple[str, ...] = ()



class Server:
Expand All @@ -18,5 +19,10 @@ async def add(self, claim: Claim) -> None:
await asyncio.sleep(0)
self.claims.append(claim)

async def ready(self, name: str) -> None:
for c in self.claims:
if c.name == name:
c.ready = True

def pending(self) -> list[Claim]:
return [c for c in self.claims if not c.ready]
34 changes: 34 additions & 0 deletions demo/ui.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Copyright 2026 Daniil Antoshin
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import { createSignal, For } from "solid-js";

interface Device {
uuid: string;
status: "allocated" | "pending" | "shared";
}

export function DeviceList(props: { devices: Device[] }) {
const [filter, setFilter] = createSignal("");
const shown = () =>
props.devices.filter((d) => d.uuid.includes(filter()));

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice — the filter reads well. includes is case-sensitive; want to lowercase both sides?

return (
<section class="devices">
<input placeholder="filter…" onInput={(e) => setFilter(e.currentTarget.value)} />
<ul>
<For each={shown()}>{(d) => <li data-status={d.status}>{d.uuid}</li>}</For>
</ul>
</section>
);
}
Loading