simtest

package
v0.6.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 13, 2026 License: MIT Imports: 28 Imported by: 0

Documentation

Overview

Package simtest is the in-process fleet-simulation scaffold described in ADR 0009. It stands up the real management server behind an httptest listener over an in-memory store, then drives it with virtual agents that speak the real enrollment and proof-of-possession poll protocol — the only thing virtualized is the Nebula node the server never observes. Tests use it to assert fleet-scale invariants (config convergence, tenant isolation, token single-use, ...) that per-request unit tests cannot reach.

It is a test-support library: it is only ever imported from _test.go files, so it is excluded from production binaries. It takes a minimal TB rather than importing the testing package directly.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Agent

type Agent struct {
	HostID      string
	Name        string
	Fingerprint string
	// contains filtered or unexported fields
}

Agent is a virtual host: an enrolled identity that speaks the real ADR 0004 poll protocol. It tracks only what an agent legitimately knows — its fingerprint and signing key — never server-side state.

func (*Agent) DrainToConverged

func (a *Agent) DrainToConverged(h *Harness, maxPolls int) int

DrainToConverged polls until the server stops shipping config (steady state), returning the number of polls. It fails the test if convergence is not reached within maxPolls — a stuck host is itself a convergence failure.

func (*Agent) Poll

func (a *Agent) Poll(h *Harness) PollResponse

Poll performs one signed GET /api/v1/agent/updates, the real ADR 0004 poll.

type Event

type Event struct {
	Step   int
	Actor  string // virtual agent name, or operator action source
	Action string // enroll | poll | bump-version | block | ...
	Target string // host ID or network ID the action touched
	Status int    // HTTP status, when applicable
	Note   string // short human-readable detail
}

Event is one entry in the simulation journal. The journal is what makes a fleet-scale failure legible (ADR 0009): instead of "assertion failed", a violated invariant prints the minimal trace of what happened to the entities involved.

type Harness

type Harness struct {
	Server  *httptest.Server
	Store   *store.SQLiteStore
	CA      *models.CA
	Journal *Journal
	// contains filtered or unexported fields
}

Harness is a running management server plus the handles a test needs to drive it: the store (for direct assertions and config-version reads), the default CA, and a shared event Journal.

func New

func New(tb TB, opts ...Option) *Harness

New brings up a fresh in-memory server with one admin operator, an API key, and a default CA — the same shape as the integration e2e harness, exposed as a reusable library.

func (*Harness) API

func (h *Harness) API(method, path string, body, out any) int

API issues an authenticated admin API request and returns the decoded JSON body (into out, if non-nil) and status code. It fails the test on transport errors so callers can focus on status assertions.

func (*Harness) APIAs

func (h *Harness) APIAs(key, method, path string, body, out any) int

APIAs is like API but authenticates with the given API key — used by multi-tenant isolation tests to act as a specific Tenant.

func (*Harness) Advance

func (h *Harness) Advance(d time.Duration)

Advance moves the harness clock forward by d (freezing at now first if it was still live).

func (*Harness) CreateHost

func (h *Harness) CreateHost(networkID, name, role, nebulaIP string) (hostID, token string)

CreateHost creates a host row and returns its ID and single-use enrollment token. role may be "" (regular host), "lighthouse", or "relay".

func (*Harness) CreateHostUnderCA

func (h *Harness) CreateHostUnderCA(networkID, caID, name, ip string) string

CreateHostUnderCA store-creates an enrolled-shaped host row owned by caID on the given network, returning its ID. Bypasses the create API so isolation tests can plant a host under one tenant's CA directly.

func (*Harness) CreateNetwork

func (h *Harness) CreateNetwork(name string, cidrs ...string) string

CreateNetwork creates a network and returns its ID.

func (*Harness) Enroll

func (h *Harness) Enroll(networkID, name, nebulaIP string) *Agent

Enroll creates and enrolls a regular host, returning a ready-to-poll Agent.

func (*Harness) NewTenant

func (h *Harness) NewTenant(name string) *Tenant

NewTenant store-creates a non-admin operator with its own API key and CA, returning a Tenant whose Key authenticates as that operator. Hosts created under the tenant's CAID are owned by it; another tenant must not be able to read or mutate them (authz gates on CA ownership — internal/api/authz.go).

func (*Harness) SetClock

func (h *Harness) SetClock(t time.Time)

SetClock freezes the harness clock at t (server + agents). Call before Advance to exercise time-dependent paths deterministically.

type Journal

type Journal struct {
	// contains filtered or unexported fields
}

Journal is a concurrency-safe append-only event log shared across a fleet.

func NewJournal

func NewJournal() *Journal

NewJournal returns an empty journal.

func (*Journal) Add

func (j *Journal) Add(e Event)

Add appends an event, stamping it with the next step number.

func (*Journal) Report

func (j *Journal) Report(target string) string

Report renders the journal entries touching target (a host or network ID) as a compact trace, for embedding in an invariant-violation failure message. An empty target renders the whole journal.

type Option

type Option func(*options)

Option configures a Harness.

func WithFileStore

func WithFileStore() Option

WithFileStore backs the harness with a temp-file SQLite database instead of ":memory:". The in-memory store pins MaxOpenConns(1), which serializes every query and masks concurrency bugs; a file-backed store uses the production pool (WAL, multiple connections), so concurrency invariants (token single-use, IP-uniqueness races) exercise the real path. Required for any test that relies on connection-level concurrency.

type PollResponse

type PollResponse struct {
	Status         int
	Reason         string   `json:"reason"` // set on 403/410 revocation bodies
	HasUpdates     bool     `json:"has_updates"`
	ConfigYAML     *string  `json:"config_yaml"`
	CertificatePEM *string  `json:"certificate_pem"`
	RekeyRequired  bool     `json:"rekey_required"`
	Blocklist      []string `json:"blocklist"`
}

PollResponse is the subset of the agent-updates response the simulation asserts against.

type TB

type TB interface {
	Helper()
	Cleanup(func())
	Fatalf(format string, args ...any)
	Errorf(format string, args ...any)
	Logf(format string, args ...any)
}

TB is the slice of *testing.T that simtest needs. Keeping it an interface avoids importing the testing package from non-test source.

type Tenant

type Tenant struct {
	Key        string
	OperatorID string
	CAID       string
}

Tenant is a non-admin operator with its own API key and CA, for multi-tenant isolation tests.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL