mirror of
https://github.com/hanzoai/compute.git
synced 2026-10-30 14:50:41 +00:00
Every cloud whose SDK would authenticate from a key held in this process is removed: Aliyun, Azure, GCP, VMware, KVM, PVE, Lightsail, direct AWS and DigitalOcean with its Kubernetes clusters and node pools, volumes, VPCs and load balancers, the BYOC cost readers and their fee, and the chain providers that committed records with a stored key. What remains, Hetzner and hosted EC2, is reached only through egress; with no carrier every cloud call refuses. A provider row names accounts by their egress label and holds no key: the ClientId, ClientSecret and key-secret fields are gone, a write carrying one is refused, and each database drops the old columns, rewrites rotation keys without their secrets and vacuums when its engine opens. The routes of the removed features are dropped with no successor. Co-authored-by: Hanzo Dev <[email protected]>
251 lines
10 KiB
Go
251 lines
10 KiB
Go
// Copyright 2024 Hanzo Industries Inc. All Rights Reserved.
|
|
//
|
|
// 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.
|
|
|
|
package object
|
|
|
|
import (
|
|
"fmt"
|
|
"path/filepath"
|
|
|
|
"github.com/hanzoai/compute/conf"
|
|
|
|
"github.com/hanzoai/orm/relational"
|
|
)
|
|
|
|
// perOrgModels are the per-TENANT tables. Under the Base backend each gets one
|
|
// physical copy per org (its own SQLite file, routed by Owner); under Postgres
|
|
// isolation is a WHERE owner=? clause over one shared table. Every model here
|
|
// carries an Owner, so it routes cleanly to a per-org DB.
|
|
func perOrgModels() []any {
|
|
return []any{
|
|
new(Asset),
|
|
new(Provider),
|
|
new(Machine),
|
|
new(Record),
|
|
new(Session),
|
|
new(Volume),
|
|
new(AgentBinding),
|
|
}
|
|
}
|
|
|
|
// sharedModels are the cross-pod SHARED tables. They live on the ONE engine
|
|
// every pod sees (Postgres under both backends), NEVER in per-org SQLite:
|
|
// - Plan is a global read-only catalog; duplicating it per org would break DRY
|
|
// and let an admin price edit diverge pod-to-pod.
|
|
// - MeterLease is a cluster-global leader-election lease; a single linearizable
|
|
// winner per hour is the whole point, which per-pod SQLite cannot provide.
|
|
//
|
|
// See LLM.md (Base backend: shared vs per-org) for the full CTO rationale.
|
|
func sharedModels() []any {
|
|
return []any{
|
|
new(Plan),
|
|
new(MeterLease),
|
|
new(BillingLease),
|
|
new(FleetWorker),
|
|
new(MeterMark),
|
|
}
|
|
}
|
|
|
|
// models is visor's full table registry -- the union of per-org and shared
|
|
// tables, defined in exactly one place. The Postgres adapter (Adapter.createTable)
|
|
// hosts every table and syncs this full set; the Base backend syncs only
|
|
// perOrgModels into each org DB and leaves sharedModels on Postgres.
|
|
func models() []any {
|
|
return append(perOrgModels(), sharedModels()...)
|
|
}
|
|
|
|
// engineProvider resolves the *relational.Engine(s) that serve visor's tables. It is
|
|
// the ONE seam between the shared-Postgres backend (a single engine for every
|
|
// owner) and the Base backend (one per-org SQLite engine plus a shared Postgres
|
|
// coordination engine). Query and model code is identical on either side; only
|
|
// the resolved engine differs.
|
|
type engineProvider interface {
|
|
// EngineFor returns the engine that serves owner's per-tenant rows.
|
|
// Postgres ignores owner (isolation is a WHERE owner=? clause); Base
|
|
// returns, lazily opening, the org's own SQLite engine.
|
|
EngineFor(owner string) (*relational.Engine, error)
|
|
// Shared returns the ONE engine that holds the cross-pod shared tables (the Plan
|
|
// catalog and the billing leases). Postgres: a single shared, linearizable engine.
|
|
// Base: the pod-local `_global` SQLite engine, made cluster-safe for the leases by
|
|
// the single-writer gate plus PullSharedLeases/PushShared across a handoff.
|
|
Shared() *relational.Engine
|
|
// PullSharedLeases merges the object store's committed billing-lease rows into the
|
|
// live coord before a claim (durable lease history across a leadership handoff).
|
|
// Postgres: no-op — the shared engine already IS the durable, linearizable source.
|
|
// Base: row-merge from the `_global` snapshot into the local coord.
|
|
PullSharedLeases() error
|
|
// PushShared ships the coord DB to the object store after a winning claim, before
|
|
// the caller debits, so the claim is durable before money moves. Postgres: no-op.
|
|
PushShared() error
|
|
// AllEngines returns every engine a cross-org sweep must union over.
|
|
// Postgres: the single engine (it already holds every org's rows).
|
|
// Base: one engine per org DB under the data root.
|
|
AllEngines() ([]*relational.Engine, error)
|
|
// Close releases every engine the provider owns.
|
|
Close() error
|
|
}
|
|
|
|
// store is the process-wide provider selected at boot by InitStore. All model
|
|
// code routes per-tenant queries through EngineFor(owner), shared-table queries
|
|
// through Shared(), and cross-org sweeps through allEngines(). Under the default
|
|
// postgres backend all three resolve to the same one engine, so the live path is
|
|
// byte-identical to before the seam existed.
|
|
var store engineProvider
|
|
|
|
// StorageBackend names the configured persistence backend.
|
|
type StorageBackend string
|
|
|
|
const (
|
|
// BackendPostgres is the historical shared-Postgres backend (default).
|
|
BackendPostgres StorageBackend = "postgres"
|
|
// BackendBase is the per-org SQLite substrate (hanzoai/base, HIP-0302).
|
|
BackendBase StorageBackend = "base"
|
|
)
|
|
|
|
// ConfiguredBackend reads the storageBackend config knob. Default is Base (SQLite
|
|
// for everything — the platform rule); a multi-instance deployment opts INTO Postgres
|
|
// with storageBackend=postgres.
|
|
func ConfiguredBackend() StorageBackend {
|
|
if conf.GetConfigString("storageBackend") == string(BackendPostgres) {
|
|
return BackendPostgres
|
|
}
|
|
return BackendBase
|
|
}
|
|
|
|
// InitStore installs the pgStore engine provider for the Postgres backend. Base
|
|
// mode is wired directly in InitAdapter (it never opens a Postgres adapter), so
|
|
// this runs only on the opt-in Postgres path.
|
|
func InitStore() {
|
|
store = &pgStore{engine: adapter.engine}
|
|
}
|
|
|
|
// EngineFor returns the storage engine that serves owner under the configured
|
|
// backend. This is the single entry point backend-agnostic code MUST use so a
|
|
// query works unchanged on either backend.
|
|
func EngineFor(owner string) (*relational.Engine, error) {
|
|
if store == nil {
|
|
return nil, fmt.Errorf("visor: store not initialised (call InitAdapter first)")
|
|
}
|
|
return store.EngineFor(owner)
|
|
}
|
|
|
|
// mustEngineFor resolves owner's engine or panics. Used only where the caller
|
|
// has no error return to thread. Under the default Postgres backend EngineFor
|
|
// hands back the one already-open engine and cannot fail, so this panics only
|
|
// under the Base backend on a broken data disk -- a fatal infra condition,
|
|
// surfaced the same way Adapter.createTable's sync failure already is.
|
|
func mustEngineFor(owner string) *relational.Engine {
|
|
engine, err := EngineFor(owner)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return engine
|
|
}
|
|
|
|
// Shared returns the engine holding the shared tables (the Plan catalog and the
|
|
// MeterLease coordination lease).
|
|
//
|
|
// It is cross-POD only under the Postgres backend, where it is the one durable
|
|
// linearizable store and the insert-once lease PK therefore holds cluster-wide.
|
|
// Under Base (the default) it is the pod-local `_global` SQLite coord, hydrated
|
|
// from and shipped to the object store — durable across a restart, but NOT
|
|
// shared between concurrent pods. There, exactly-once rests on the single-writer
|
|
// election in coordinator.go, not on the PK.
|
|
//
|
|
// This comment used to read "Postgres under both backends", which is false for
|
|
// Base and is precisely why the billing gate's importance was invisible: it made
|
|
// the election look like belt-and-braces over a global constraint when it is in
|
|
// fact the only thing standing between two pods and a double debit.
|
|
func Shared() *relational.Engine {
|
|
return store.Shared()
|
|
}
|
|
|
|
// Ready reports whether visor can still reach the store every route depends on.
|
|
// It is the question a readiness probe asks, answered in the ONE package that
|
|
// can answer it: the backend is chosen here and `store` is unexported, so no
|
|
// caller outside object could form the answer without a second copy of the
|
|
// selection rule.
|
|
//
|
|
// It pings the SHARED engine and not every org's, because the shared engine is
|
|
// the one both backends always have, and a fan-out over per-org SQLite would
|
|
// turn a probe that runs every ten seconds into one file open per tenant — a
|
|
// health check whose cost grows with the customer list is an outage waiting for
|
|
// a big enough customer list.
|
|
//
|
|
// A nil store is unready rather than an error to shout about: it is what visor
|
|
// looks like between process start and InitAdapter, which is exactly the window
|
|
// a readiness probe exists to keep traffic out of.
|
|
func Ready() error {
|
|
if store == nil {
|
|
return fmt.Errorf("visor: store not initialised")
|
|
}
|
|
e := store.Shared()
|
|
if e == nil {
|
|
return fmt.Errorf("visor: no shared engine")
|
|
}
|
|
return e.Ping()
|
|
}
|
|
|
|
// allEngines returns every engine a cross-org sweep (billing node-pool report,
|
|
// stale-session GC) must union over. One engine under Postgres; one per org DB
|
|
// under Base.
|
|
func allEngines() ([]*relational.Engine, error) {
|
|
if store == nil {
|
|
return nil, fmt.Errorf("visor: store not initialised (call InitAdapter first)")
|
|
}
|
|
return store.AllEngines()
|
|
}
|
|
|
|
// pullSharedLeases merges the durable billing-lease rows into the live coord before a
|
|
// claim (guarantee 2 of exactly-once). Backend-agnostic: a no-op under Postgres, a
|
|
// row-merge from the `_global` snapshot under Base.
|
|
func pullSharedLeases() error {
|
|
if store == nil {
|
|
return fmt.Errorf("visor: store not initialised (call InitAdapter first)")
|
|
}
|
|
return store.PullSharedLeases()
|
|
}
|
|
|
|
// pushShared ships the coord DB after a winning claim, before the caller debits, so
|
|
// the claim is durable before money moves. No-op under Postgres.
|
|
func pushShared() error {
|
|
if store == nil {
|
|
return fmt.Errorf("visor: store not initialised (call InitAdapter first)")
|
|
}
|
|
return store.PushShared()
|
|
}
|
|
|
|
// dataRoot is the filesystem root for per-org Base SQLite files (default /data).
|
|
func dataRoot() string {
|
|
if r := conf.GetConfigString("dataRoot"); r != "" {
|
|
return r
|
|
}
|
|
return "/data"
|
|
}
|
|
|
|
// DBPath is the HIP-0302 per-org SQLite path for a visor org DB. It mirrors the
|
|
// layout hanzo/cloud uses (orgs/<org>/<service>.db) so every Hanzo service that
|
|
// moves onto Base shares one on-disk convention.
|
|
//
|
|
// DBPath("/data", "acme") -> /data/orgs/acme/visor.db
|
|
//
|
|
// The empty owner (catalog / cluster-global rows that no org owns) routes to the
|
|
// _global sentinel, matching hanzo/cloud's fail-loud migration bucket.
|
|
func DBPath(root, owner string) string {
|
|
if owner == "" {
|
|
owner = "_global"
|
|
}
|
|
return filepath.Join(root, "orgs", owner, "visor.db")
|
|
}
|