Add minimal control plane with REST API, database, and reconciler
- REST API (chi router): sandbox CRUD, exec, pause/resume, file write/read - PostgreSQL persistence via pgx/v5 + sqlc (sandboxes table with goose migration) - Connect RPC client to host agent for all VM operations - Reconciler syncs host agent state with DB every 30s (detects TTL-reaped sandboxes) - OpenAPI 3.1 spec served at /openapi.yaml, Swagger UI at /docs - Added WriteFile/ReadFile RPCs to hostagent proto and implementations - File upload via multipart form, download via JSON body POST - sandbox_id propagated from control plane to host agent on create
This commit is contained in:
32
internal/db/db.go
Normal file
32
internal/db/db.go
Normal file
@ -0,0 +1,32 @@
|
||||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.30.0
|
||||
|
||||
package db
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/jackc/pgx/v5"
|
||||
"github.com/jackc/pgx/v5/pgconn"
|
||||
)
|
||||
|
||||
type DBTX interface {
|
||||
Exec(context.Context, string, ...interface{}) (pgconn.CommandTag, error)
|
||||
Query(context.Context, string, ...interface{}) (pgx.Rows, error)
|
||||
QueryRow(context.Context, string, ...interface{}) pgx.Row
|
||||
}
|
||||
|
||||
func New(db DBTX) *Queries {
|
||||
return &Queries{db: db}
|
||||
}
|
||||
|
||||
type Queries struct {
|
||||
db DBTX
|
||||
}
|
||||
|
||||
func (q *Queries) WithTx(tx pgx.Tx) *Queries {
|
||||
return &Queries{
|
||||
db: tx,
|
||||
}
|
||||
}
|
||||
26
internal/db/models.go
Normal file
26
internal/db/models.go
Normal file
@ -0,0 +1,26 @@
|
||||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.30.0
|
||||
|
||||
package db
|
||||
|
||||
import (
|
||||
"github.com/jackc/pgx/v5/pgtype"
|
||||
)
|
||||
|
||||
type Sandbox struct {
|
||||
ID string `json:"id"`
|
||||
OwnerID string `json:"owner_id"`
|
||||
HostID string `json:"host_id"`
|
||||
Template string `json:"template"`
|
||||
Status string `json:"status"`
|
||||
Vcpus int32 `json:"vcpus"`
|
||||
MemoryMb int32 `json:"memory_mb"`
|
||||
TimeoutSec int32 `json:"timeout_sec"`
|
||||
GuestIp string `json:"guest_ip"`
|
||||
HostIp string `json:"host_ip"`
|
||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||
StartedAt pgtype.Timestamptz `json:"started_at"`
|
||||
LastActiveAt pgtype.Timestamptz `json:"last_active_at"`
|
||||
LastUpdated pgtype.Timestamptz `json:"last_updated"`
|
||||
}
|
||||
286
internal/db/sandboxes.sql.go
Normal file
286
internal/db/sandboxes.sql.go
Normal file
@ -0,0 +1,286 @@
|
||||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.30.0
|
||||
// source: sandboxes.sql
|
||||
|
||||
package db
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/jackc/pgx/v5/pgtype"
|
||||
)
|
||||
|
||||
const bulkUpdateStatusByIDs = `-- name: BulkUpdateStatusByIDs :exec
|
||||
UPDATE sandboxes
|
||||
SET status = $2,
|
||||
last_updated = NOW()
|
||||
WHERE id = ANY($1::text[])
|
||||
`
|
||||
|
||||
type BulkUpdateStatusByIDsParams struct {
|
||||
Column1 []string `json:"column_1"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
func (q *Queries) BulkUpdateStatusByIDs(ctx context.Context, arg BulkUpdateStatusByIDsParams) error {
|
||||
_, err := q.db.Exec(ctx, bulkUpdateStatusByIDs, arg.Column1, arg.Status)
|
||||
return err
|
||||
}
|
||||
|
||||
const getSandbox = `-- name: GetSandbox :one
|
||||
SELECT id, owner_id, host_id, template, status, vcpus, memory_mb, timeout_sec, guest_ip, host_ip, created_at, started_at, last_active_at, last_updated FROM sandboxes WHERE id = $1
|
||||
`
|
||||
|
||||
func (q *Queries) GetSandbox(ctx context.Context, id string) (Sandbox, error) {
|
||||
row := q.db.QueryRow(ctx, getSandbox, id)
|
||||
var i Sandbox
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.OwnerID,
|
||||
&i.HostID,
|
||||
&i.Template,
|
||||
&i.Status,
|
||||
&i.Vcpus,
|
||||
&i.MemoryMb,
|
||||
&i.TimeoutSec,
|
||||
&i.GuestIp,
|
||||
&i.HostIp,
|
||||
&i.CreatedAt,
|
||||
&i.StartedAt,
|
||||
&i.LastActiveAt,
|
||||
&i.LastUpdated,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const insertSandbox = `-- name: InsertSandbox :one
|
||||
INSERT INTO sandboxes (id, owner_id, host_id, template, status, vcpus, memory_mb, timeout_sec)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
|
||||
RETURNING id, owner_id, host_id, template, status, vcpus, memory_mb, timeout_sec, guest_ip, host_ip, created_at, started_at, last_active_at, last_updated
|
||||
`
|
||||
|
||||
type InsertSandboxParams struct {
|
||||
ID string `json:"id"`
|
||||
OwnerID string `json:"owner_id"`
|
||||
HostID string `json:"host_id"`
|
||||
Template string `json:"template"`
|
||||
Status string `json:"status"`
|
||||
Vcpus int32 `json:"vcpus"`
|
||||
MemoryMb int32 `json:"memory_mb"`
|
||||
TimeoutSec int32 `json:"timeout_sec"`
|
||||
}
|
||||
|
||||
func (q *Queries) InsertSandbox(ctx context.Context, arg InsertSandboxParams) (Sandbox, error) {
|
||||
row := q.db.QueryRow(ctx, insertSandbox,
|
||||
arg.ID,
|
||||
arg.OwnerID,
|
||||
arg.HostID,
|
||||
arg.Template,
|
||||
arg.Status,
|
||||
arg.Vcpus,
|
||||
arg.MemoryMb,
|
||||
arg.TimeoutSec,
|
||||
)
|
||||
var i Sandbox
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.OwnerID,
|
||||
&i.HostID,
|
||||
&i.Template,
|
||||
&i.Status,
|
||||
&i.Vcpus,
|
||||
&i.MemoryMb,
|
||||
&i.TimeoutSec,
|
||||
&i.GuestIp,
|
||||
&i.HostIp,
|
||||
&i.CreatedAt,
|
||||
&i.StartedAt,
|
||||
&i.LastActiveAt,
|
||||
&i.LastUpdated,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const listSandboxes = `-- name: ListSandboxes :many
|
||||
SELECT id, owner_id, host_id, template, status, vcpus, memory_mb, timeout_sec, guest_ip, host_ip, created_at, started_at, last_active_at, last_updated FROM sandboxes ORDER BY created_at DESC
|
||||
`
|
||||
|
||||
func (q *Queries) ListSandboxes(ctx context.Context) ([]Sandbox, error) {
|
||||
rows, err := q.db.Query(ctx, listSandboxes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []Sandbox
|
||||
for rows.Next() {
|
||||
var i Sandbox
|
||||
if err := rows.Scan(
|
||||
&i.ID,
|
||||
&i.OwnerID,
|
||||
&i.HostID,
|
||||
&i.Template,
|
||||
&i.Status,
|
||||
&i.Vcpus,
|
||||
&i.MemoryMb,
|
||||
&i.TimeoutSec,
|
||||
&i.GuestIp,
|
||||
&i.HostIp,
|
||||
&i.CreatedAt,
|
||||
&i.StartedAt,
|
||||
&i.LastActiveAt,
|
||||
&i.LastUpdated,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const listSandboxesByHostAndStatus = `-- name: ListSandboxesByHostAndStatus :many
|
||||
SELECT id, owner_id, host_id, template, status, vcpus, memory_mb, timeout_sec, guest_ip, host_ip, created_at, started_at, last_active_at, last_updated FROM sandboxes
|
||||
WHERE host_id = $1 AND status = ANY($2::text[])
|
||||
ORDER BY created_at DESC
|
||||
`
|
||||
|
||||
type ListSandboxesByHostAndStatusParams struct {
|
||||
HostID string `json:"host_id"`
|
||||
Column2 []string `json:"column_2"`
|
||||
}
|
||||
|
||||
func (q *Queries) ListSandboxesByHostAndStatus(ctx context.Context, arg ListSandboxesByHostAndStatusParams) ([]Sandbox, error) {
|
||||
rows, err := q.db.Query(ctx, listSandboxesByHostAndStatus, arg.HostID, arg.Column2)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []Sandbox
|
||||
for rows.Next() {
|
||||
var i Sandbox
|
||||
if err := rows.Scan(
|
||||
&i.ID,
|
||||
&i.OwnerID,
|
||||
&i.HostID,
|
||||
&i.Template,
|
||||
&i.Status,
|
||||
&i.Vcpus,
|
||||
&i.MemoryMb,
|
||||
&i.TimeoutSec,
|
||||
&i.GuestIp,
|
||||
&i.HostIp,
|
||||
&i.CreatedAt,
|
||||
&i.StartedAt,
|
||||
&i.LastActiveAt,
|
||||
&i.LastUpdated,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const updateLastActive = `-- name: UpdateLastActive :exec
|
||||
UPDATE sandboxes
|
||||
SET last_active_at = $2,
|
||||
last_updated = NOW()
|
||||
WHERE id = $1
|
||||
`
|
||||
|
||||
type UpdateLastActiveParams struct {
|
||||
ID string `json:"id"`
|
||||
LastActiveAt pgtype.Timestamptz `json:"last_active_at"`
|
||||
}
|
||||
|
||||
func (q *Queries) UpdateLastActive(ctx context.Context, arg UpdateLastActiveParams) error {
|
||||
_, err := q.db.Exec(ctx, updateLastActive, arg.ID, arg.LastActiveAt)
|
||||
return err
|
||||
}
|
||||
|
||||
const updateSandboxRunning = `-- name: UpdateSandboxRunning :one
|
||||
UPDATE sandboxes
|
||||
SET status = 'running',
|
||||
host_ip = $2,
|
||||
guest_ip = $3,
|
||||
started_at = $4,
|
||||
last_active_at = $4,
|
||||
last_updated = NOW()
|
||||
WHERE id = $1
|
||||
RETURNING id, owner_id, host_id, template, status, vcpus, memory_mb, timeout_sec, guest_ip, host_ip, created_at, started_at, last_active_at, last_updated
|
||||
`
|
||||
|
||||
type UpdateSandboxRunningParams struct {
|
||||
ID string `json:"id"`
|
||||
HostIp string `json:"host_ip"`
|
||||
GuestIp string `json:"guest_ip"`
|
||||
StartedAt pgtype.Timestamptz `json:"started_at"`
|
||||
}
|
||||
|
||||
func (q *Queries) UpdateSandboxRunning(ctx context.Context, arg UpdateSandboxRunningParams) (Sandbox, error) {
|
||||
row := q.db.QueryRow(ctx, updateSandboxRunning,
|
||||
arg.ID,
|
||||
arg.HostIp,
|
||||
arg.GuestIp,
|
||||
arg.StartedAt,
|
||||
)
|
||||
var i Sandbox
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.OwnerID,
|
||||
&i.HostID,
|
||||
&i.Template,
|
||||
&i.Status,
|
||||
&i.Vcpus,
|
||||
&i.MemoryMb,
|
||||
&i.TimeoutSec,
|
||||
&i.GuestIp,
|
||||
&i.HostIp,
|
||||
&i.CreatedAt,
|
||||
&i.StartedAt,
|
||||
&i.LastActiveAt,
|
||||
&i.LastUpdated,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const updateSandboxStatus = `-- name: UpdateSandboxStatus :one
|
||||
UPDATE sandboxes
|
||||
SET status = $2,
|
||||
last_updated = NOW()
|
||||
WHERE id = $1
|
||||
RETURNING id, owner_id, host_id, template, status, vcpus, memory_mb, timeout_sec, guest_ip, host_ip, created_at, started_at, last_active_at, last_updated
|
||||
`
|
||||
|
||||
type UpdateSandboxStatusParams struct {
|
||||
ID string `json:"id"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
func (q *Queries) UpdateSandboxStatus(ctx context.Context, arg UpdateSandboxStatusParams) (Sandbox, error) {
|
||||
row := q.db.QueryRow(ctx, updateSandboxStatus, arg.ID, arg.Status)
|
||||
var i Sandbox
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.OwnerID,
|
||||
&i.HostID,
|
||||
&i.Template,
|
||||
&i.Status,
|
||||
&i.Vcpus,
|
||||
&i.MemoryMb,
|
||||
&i.TimeoutSec,
|
||||
&i.GuestIp,
|
||||
&i.HostIp,
|
||||
&i.CreatedAt,
|
||||
&i.StartedAt,
|
||||
&i.LastActiveAt,
|
||||
&i.LastUpdated,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
Reference in New Issue
Block a user