relaynode

package
v0.9.77 Latest Latest
Warning

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

Go to latest
Published: Nov 6, 2026 License: GPL-3.0 Imports: 26 Imported by: 0

Documentation

Overview

Package relaynode is the API server's registry of standalone relay nodes. A platform admin registers and retires nodes; a periodic probe reads each node's GET /lifecycle; relay credentials list only the nodes that are eligible, preferring the client's region.

Index

Constants

View Source
const (
	StateKnown   = "known"
	StateRetired = "retired"
)

Registration states.

View Source
const (
	FailureInvalidURL   = "invalid lifecycle_url"
	FailureBlocked      = "destination not allowed"
	FailureTimeout      = "timeout"
	FailureConnection   = "connection failed"
	FailureUnauthorized = "unauthorized"
	FailureInvalidJSON  = "invalid /lifecycle JSON"
	FailureUnknownState = "unknown lifecycle state"
	FailureNotRelay     = "role is not relay"
)

Probe failure reasons. They are stable strings, so a repeated failure can be recognised and the admin list can show why a node is unhealthy; the changing details, such as an error with an ephemeral port, go to the log.

View Source
const DefaultProbeTimeout = 3 * time.Second

DefaultProbeTimeout bounds one GET /lifecycle. It stays well under ProbeInterval so a slow node cannot stack probes.

View Source
const FreshnessWindow = 3 * ProbeInterval

FreshnessWindow is how old a node's last good probe may be before the node stops being advertised: three probe intervals.

View Source
const ProbeInterval = 10 * time.Second

ProbeInterval is how often the probe job reads every known node.

Variables

View Source
var ErrDuplicate = errors.New("relaynode: a known node already uses this lifecycle_url")

ErrDuplicate is returned when a known node already uses the lifecycle URL.

View Source
var ErrInvalid = errors.New("relaynode: invalid node")

ErrInvalid marks a Spec that fails validation.

View Source
var ErrNoEligibleNodes = errors.New("relaynode: no eligible relay nodes")

ErrNoEligibleNodes is returned when no node can be advertised.

View Source
var ErrNotFound = errors.New("relaynode: node not found")

ErrNotFound is returned for an unknown node id.

Functions

This section is empty.

Types

type Collector

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

Collector reports each known node's eligibility and last probe result at scrape time, so an alert can see a node that serves but fails its probe. Every API server reads the same registry; aggregate with max by (node_id).

func NewCollector

func NewCollector(list func(context.Context) ([]Node, error)) *Collector

NewCollector returns a Collector over list, normally Store.List.

func (*Collector) Collect

func (c *Collector) Collect(ch chan<- prometheus.Metric)

Collect implements prometheus.Collector. A failed read reports no nodes rather than failing the whole scrape.

func (*Collector) Describe

func (c *Collector) Describe(ch chan<- *prometheus.Desc)

Describe implements prometheus.Collector.

type EligibleFunc

type EligibleFunc func(ctx context.Context) ([]Node, error)

EligibleFunc lists the nodes relay credentials may advertise.

type Monitor

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

Monitor probes every known node and records the results. It logs a failure when the node's stored last_probe_error changes, and a recovery when a failing node succeeds. The comparison uses the result each write replaced, so the log follows the database rather than one process's memory: any API server can run the probe.

func NewMonitor

func NewMonitor(store ProbeStore, prober *Prober, logger *slog.Logger) *Monitor

NewMonitor returns a Monitor.

func (*Monitor) ProbeAll

func (m *Monitor) ProbeAll(ctx context.Context) error

ProbeAll probes every known node concurrently and records each result. It returns the first store error; probe failures are recorded, not returned.

func (*Monitor) Run

func (m *Monitor) Run(ctx context.Context, interval time.Duration)

Run probes every interval until ctx ends, starting at once. It is the fallback when River, which normally schedules the probe, is unavailable.

type Node

type Node struct {
	ID           int64    `json:"id" example:"7"`
	Region       string   `json:"region" example:"us-east"`
	TURNURLs     []string `json:"turn_urls" example:"turn:relay-1.example.com:3478?transport=udp"`
	LifecycleURL string   `json:"lifecycle_url" example:"http://10.0.0.5:9091/lifecycle"`
	State        string   `json:"state" enum:"known,retired" example:"known"`
	// Eligible reports whether credentials currently advertise this node.
	Eligible    bool       `json:"eligible" example:"true"`
	LastProbeAt *time.Time `json:"last_probe_at" example:"2026-10-26T12:00:00Z"`
	LastProbeOK bool       `json:"last_probe_ok" example:"true"`
	// LastProbeError is why the last probe failed, such as "timeout" or
	// "status 503". nil when it succeeded or has not run.
	LastProbeError *string `json:"last_probe_error" example:"timeout"`
	// LastLifecycleState and LastActiveAllocations are the state and
	// active_work the last probe read. nil when it read no valid /lifecycle
	// document.
	LastLifecycleState    *string   `json:"last_lifecycle_state" example:"active"`
	LastActiveAllocations *int64    `json:"last_active_allocations" example:"12"`
	CreatedAt             time.Time `json:"created_at" example:"2026-10-26T11:00:00Z"`
	UpdatedAt             time.Time `json:"updated_at" example:"2026-10-26T12:00:00Z"`
}

Node is one registered relay node as the admin API reports it.

type PriorProbe

type PriorProbe struct {
	At    *time.Time
	OK    bool
	Error *string
}

PriorProbe is a node's stored probe result just before a new one replaced it.

type ProbeStore

type ProbeStore interface {
	Known(ctx context.Context) ([]Node, error)
	RecordProbe(ctx context.Context, id int64, r Result) (prior PriorProbe, recorded bool, err error)
}

ProbeStore is the part of Store the Monitor needs.

type Prober

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

Prober reads a node's GET /lifecycle.

func NewProber

func NewProber(token string, timeout time.Duration) *Prober

NewProber returns a Prober that sends "Authorization: Bearer <token>" when token is set and gives up on a node after timeout. Every connection passes the destination policy in checkProbeTarget.

func (*Prober) Probe

func (p *Prober) Probe(ctx context.Context, lifecycleURL string) Result

Probe reads one node. It never returns an error: every failure is a Result with OK false.

type Result

type Result struct {
	// OK is true when the node answered 2xx with a valid relay status. A
	// draining node is OK; eligibility also needs State "active".
	OK bool
	// State is the lifecycle state read, or "" when none could be read.
	State string
	// ActiveWork is the active_work read, or nil when none could be read.
	ActiveWork *int64
	// Unauthorized is true when the node answered 401: RELAY_PROBE_TOKEN does
	// not match the node's LIFECYCLE_TOKEN.
	Unauthorized bool
	// Failure is the stable reason OK is false: one of the Failure constants
	// or "status <code>".
	Failure string
	// Detail is the underlying error or value, for the log only.
	Detail string
}

Result is one probe of a node's GET /lifecycle.

type Selection

type Selection struct {
	URLs []string
	// Region is the region served, or "" when the nodes span regions.
	Region string
}

Selection is the relay URLs one credential set advertises.

type Selector

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

Selector picks the relay URLs for a credential request. Safe for concurrent use.

func NewSelector

func NewSelector(eligible EligibleFunc, embedded *Node) *Selector

NewSelector returns a Selector over eligible. embedded, when non-nil, is the relay running inside this API server: it has no region, it is always eligible while the process runs, and it is served only in the cross-region fallback.

func (*Selector) Select

func (s *Selector) Select(ctx context.Context, region string) (Selection, error)

Select returns the URLs of every eligible node in region, in shuffled node order. region is matched case-insensitively. With no eligible node in region, or no region, it falls back to every eligible node. Each node's URLs keep their registered order, so a client that tries them in order still prefers that node's first transport. When the eligible nodes cannot be read, the embedded relay is served alone if there is one.

type Spec

type Spec struct {
	Region       string
	TURNURLs     []string
	LifecycleURL string
}

Spec is what an admin supplies to register a node.

func (Spec) Validate

func (s Spec) Validate() error

Validate reports a wrapped ErrInvalid for the first bad field.

type Store

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

Store reads and writes the relay_nodes table. The table is platform-global, so every query runs outside a tenant context.

func NewStore

func NewStore(pool *db.Pool) *Store

NewStore returns a Store on pool.

func (*Store) Create

func (s *Store) Create(ctx context.Context, spec Spec) (Node, error)

Create validates spec and registers a known node. It returns a wrapped ErrInvalid or ErrDuplicate for a bad request.

func (*Store) Eligible

func (s *Store) Eligible(ctx context.Context) ([]Node, error)

Eligible returns the nodes relay credentials may advertise.

func (*Store) Known

func (s *Store) Known(ctx context.Context) ([]Node, error)

Known returns the nodes the probe job reads.

func (*Store) List

func (s *Store) List(ctx context.Context) ([]Node, error)

List returns every node, retired ones included, oldest first. Eligible is computed by the database, with the same rule and clock as Eligible.

func (*Store) RecordProbe

func (s *Store) RecordProbe(ctx context.Context, id int64, r Result) (prior PriorProbe, recorded bool, err error)

RecordProbe stores one probe result and returns the result it replaced, read under a row lock so concurrent probers each see the other's write. recorded is false when the node was retired since it was listed; such a row is left alone.

func (*Store) Retire

func (s *Store) Retire(ctx context.Context, id int64) (Node, error)

Retire stops advertising and probing a node. It keeps the row and is idempotent. It returns ErrNotFound for an unknown id.

Jump to

Keyboard shortcuts

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