diff --git a/README.md b/README.md index a1a0556..6e23109 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,14 @@ per-cloud glue. The API follows a Karpenter-style split: Nebula architecture: the operator's webhook, virtual kubelet and placement controller drive NodePools and NodeClaims onto hyperscalers, NeoClouds, Kubernetes clusters and on-prem servers

+## Features + +- **Kubernetes-native experience.** Existing Deployments and Jobs opt in via labels; no new workload type and no per-cloud glue. +- **Multi-provider support.** Modal and AWS today, with more to come. +- **Policy-driven placement.** Flexible NodePool spec to pick providers by cost, availability, and region, with failover and network egress policy. +- **Cost tracking.** Out-of-the-box per-instance cost tracking. +- **Observability.** Rich metrics and logs for placement, provisioning, and instance lifecycle. + ## How it works 1. **Opt in.** You label a Pod to request an accelerator. @@ -64,6 +72,11 @@ spec: strategy: Ordered # try providers in listed order (or LowestPrice) failover: blocklistTTL: 10m # how long a failed placement is skipped + egress: # outbound policy; omitted = Open + mode: Allowlist # or Blocked to permit nothing + targets: # CIDRs, bare IPs and domains, wildcards allowed + - 10.0.0.0/8 + - "*.huggingface.co" ``` ## Opting a workload in diff --git a/pkg/provider/catalog/catalog.go b/pkg/provider/catalog/catalog.go index 9e709fa..9de75ff 100644 --- a/pkg/provider/catalog/catalog.go +++ b/pkg/provider/catalog/catalog.go @@ -192,8 +192,8 @@ func parseCSV(r io.Reader) ([]provider.Offering, error) { // ParseFloat takes "NaN" and "Inf" as valid floats. Refused at the boundary because a rate // is pinned onto the claim and integrated from there — the damage is downstream, in a // durable ledger and a counter that cannot be corrected, not in this row. - if math.IsNaN(price) || math.IsInf(price, 0) { - return nil, fmt.Errorf("row %d: price_per_hour %q is not a finite number", i, field(rec, colPrice)) + if math.IsNaN(price) || math.IsInf(price, 0) || price < 0 { + return nil, fmt.Errorf("row %d: price_per_hour %q must be a non-negative finite number", i, field(rec, colPrice)) } available, err := strconv.ParseBool(field(rec, colAvailable)) if err != nil {