Help us shape the future of Go! Take the 2026 Go Developer Survey

api

package
v1.3.104 Latest Latest
Warning

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

Go to latest
Published: Oct 31, 2026 License: AGPL-3.0 Imports: 31 Imported by: 0

Documentation

Overview

Package api pkg/deployment/sd/api/api.go c4-net-discovery

Package api pkg/deployment/sd/api/charts.go

Package api pkg/deployment/sd/api/cxo_publisher.go c4-net-discovery

CXO publisher for service-discovery's services tree.

Subscribers (the hypervisor's network visualizer + tab-specific consumers) read the live set of registered services from a single TreeStore feed instead of polling /api/services?type=... over HTTP.

Wire shape: ONE batched leaf per service type

Each service type gets exactly one leaf carrying that type's whole live service set:

services/<type>/all        // FrameGzip(v1, JSON []servicedisc.Service)

Older builds published one leaf PER service at services/<type>/<pk>/entry (+ an optional /tombstone) — O(#services), one tiny object per registered service. A large deployment's Root then could not finish filling over a subscriber's short-lived delivering dmsg conn. Sharding by type collapses the object count to O(#types) (~10). The batch leaf lives UNDER the type dir (…/all, not a bare services/<type>) so every existing consumer prefix — services/, services/visor/, services/<type>/ — still walks straight onto it; a service PK is 66 hex chars and never collides with the "all" segment.

Encoding is JSON+gzip, not fixed-layout binary: servicedisc.Service carries several optional, variable-length nested structs (Geo, VPNInfo, CoinInfo, LocalIPs), so it is the "awkward for binary" case — unlike telemetrywire's fixed rows. Services are sorted by PK so an unchanged set re-encodes to identical bytes (a CXO content-addressed wire no-op), then version-framed + gzipped (cxoutils.FrameGzip). The version byte gates the format: a bumped version is rejected cleanly by an old reader rather than misparsed.

Interop / deploy order

Deploy is SERVICE-FIRST. A not-yet-upgraded reader walking a type prefix looks for the OLD .../<pk>/entry leaves, finds only the batched …/all leaf, reports an empty snapshot for that type, and falls back to HTTP — safe degradation. Upgraded readers prefer the batched leaf and still parse OLD per-service leaves, so either publisher shape resolves.

A deregistration / expiry is the ABSENCE of a service from its type's batched leaf — there is no tombstone leaf class anymore (the old per-service tombstones were write-only across the codebase).

Heartbeat short-circuit

SD's register/heartbeat rate is high. The worker holds the live per-type service set and, on a re-register, compares the new marshaled Service against the stored one; when the materially-visible content is unchanged it skips the re-encode entirely, so a steady heartbeat stream does not churn the tree.

HTTP-path decoupling: PutEntry / DelEntry are non-blocking. The HTTP register / deregister handlers call them inline, but the underlying treestore.Publisher mutex is contended by subscriber I/O — under load (many concurrent subscribers) a synchronous Put can block the HTTP goroutine long enough to pile up thousands of pending registrations and stall the entire visor refresh loop. Instead we route every publish through a single buffered-channel worker and drop on overflow. The per-type service state map is owned by that single worker goroutine, so its mutations need no lock. CXO data quality (last-write-wins, up to publishQueueDepth events in flight) degrades gracefully; the HTTP path stays fast.

Package api pkg/deployment/sd/api/service_ingest.go c4-net-discovery

SD-registration-over-CXO ingest: the server side of the fan-in path where visors publish their live service-entry set as a CXO feed instead of re-POSTing every entry over a fresh dmsg stream every 90s (each with a full Noise handshake — the secp256k1 handshakeResponder that dominates discovery -service CPU). The CXO aggregator (pkg/deployment/sd/regcxo) calls IngestServiceFromCXO once per entry in each batch it replicates.

This is purely ADDITIVE and dual-write: the HTTP POST /api/services path remains authoritative and keeps writing the same store on the same 90s schedule. The CXO ingest therefore never clobbers a fresh HTTP register:

  • When a record already exists (the common case — HTTP registered first), the CXO ingest is a keepalive: it re-writes the STORED entry to refresh its TTL, off a warm CXO connection instead of a fresh handshake. It writes back exactly what is there, so it can never overwrite a newer HTTP registration (mirrors the AR's bind keepalive, which likewise refreshes off the stored record rather than the incoming one).
  • When no record exists yet, only the non-visor types take a fresh insert. A type=visor registration is gated on ipIsPublic(entry, observed-remote- addr), and the CXO path has no observed remote address to check against — so a fresh visor entry is left to HTTP, exactly as a fresh SUDPH bind is left to the AR's UDP path.

Deregistration is ABSENCE, not a tombstone: a service the visor stops publishing simply stops being refreshed here and expires on the store's existing entry TTL (Config.EntryTimeout). There is deliberately no CXO delete path.

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrInvalidJSON is returned when json sent is invalid.
	ErrInvalidJSON = errors.New("invalid json")
	// ErrPKMismatch is returned on a PK mismatch.
	ErrPKMismatch = errors.New("using public key other than your own is forbidden")
	// ErrVisorVersionIsTooOld returned when visor version is too old
	ErrVisorVersionIsTooOld = errors.New("visor version is too old")
	// ErrMissingType is returned when there is no type in request.
	ErrMissingType = errors.New("missing service type in request")
	// ErrFailedToGetGeoData is returned when we are unable to get the geo data.
	ErrFailedToGetGeoData = errors.New("failed to get visor data")
)
View Source
var WhitelistPKs = httpauth.NewWhitelist()

WhitelistPKs holds the keys allowed to call the bulk deregistration endpoint.

Functions

func BatchLeafPath

func BatchLeafPath(svcType string) string

BatchLeafPath is exported so subscribers can construct the batched per-type leaf path without duplicating the format string.

Types

type API

type API struct {
	// Stats, when set, adds the process and traffic charts to the status page.
	Stats *charts.ServiceStats

	// Handler is the chi router, built once in New. Every sibling
	// service (tpd, ar, dmsg-discovery) does the same; SD used to
	// rebuild the whole router — middleware stack and all routes —
	// on every single request before serving it.
	http.Handler

	DmsgServers []string
	// contains filtered or unexported fields
}

API represents the service-discovery API.

func New

func New(log logrus.FieldLogger, db store.Store, nonceDB httpauth.NonceStore,
	enableMetrics bool, m sdmetrics.Metrics, dmsgAddr, geoipURL string) *API

New creates an API.

func (*API) BackfillDHTMirror

func (a *API) BackfillDHTMirror(ctx context.Context, log logrus.FieldLogger)

BackfillDHTMirror mirrors the full per-visor service list for every visor that currently has at least one service registered. Producing one DHT item per visor (value = all that visor's services) matches HTTP discovery and replaces the pre-#2333 per-service mirror which overwrote the same target for visors offering multiple service types.

func (*API) Charts added in v1.3.101

func (a *API) Charts() *charts.Page

Charts is the charts page, or nil until StartCharts runs.

func (*API) ChartsPage added in v1.3.100

func (a *API) ChartsPage(w http.ResponseWriter, r *http.Request)

ChartsPage serves the charts page, or 404 when charts are not running.

func (*API) IngestServiceFromCXO

func (a *API) IngestServiceFromCXO(ctx context.Context, reporter cipher.PubKey, se servicedisc.Service)

IngestServiceFromCXO applies one service entry received over the SD-registration-over-CXO feed. reporter is the feed's publisher PK (the visor); an entry whose address is not reporter's own is DROPPED, so a visor can only ever register its own services — the ownership check the HTTP path enforces via httpauth is structural here.

See the package doc for the dual-write / no-clobber semantics.

func (*API) RunBackgroundTasks

func (a *API) RunBackgroundTasks(ctx context.Context, log logrus.FieldLogger)

RunBackgroundTasks is goroutine which runs in background periodic tasks of skycoin-service-discovery.

func (*API) SetDHTMirror

func (a *API) SetDHTMirror(m interface {
	Mirror(subjectPK cipher.PubKey, entry interface{}, seq uint64)
	Delete(subjectPK cipher.PubKey)
})

SetDHTMirror sets a mirror that publishes per-visor service lists to the DHT on every registration and clears them on full deregistration.

func (*API) SetServicesCXOPublisher

func (a *API) SetServicesCXOPublisher(p *ServicesCXOPublisher)

SetServicesCXOPublisher installs the CXO publisher that mirrors register / deregister events into a TreeStore feed. Pass nil to disable. Wired by cmd/svc/service-discovery/commands/root.go after the publisher is built; no-op until then.

func (*API) StartCharts added in v1.3.100

func (a *API) StartCharts(ctx context.Context, st charts.Store, log logrus.FieldLogger)

StartCharts samples the registered services into st until ctx ends and serves them as the charts page.

func (*API) WarmCXOFromStore

func (a *API) WarmCXOFromStore(ctx context.Context) int

WarmCXOFromStore pre-populates the CXO publisher's tree from the current SD store. Without this, the publisher only Puts on register/heartbeat events — so right after an SD restart the publisher's tree is empty until the first event lands, and any subscriber that connects in the gap times out at firstSyncTimeout (10s) with "timeout waiting for Root".

Best-effort: per-type listing failures are logged and skipped, but the returned count is the actual number of entries pushed into the publisher's queue. Caller should run this once after SetServicesCXOPublisher and before declaring SD ready to serve.

type HealthCheckResponse

type HealthCheckResponse = httputil.HealthCheckResponse

HealthCheckResponse is the /health body every service shares.

type ServicesCXOPublisher

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

ServicesCXOPublisher mirrors SD's services state into a CXO TreeStore feed. Started automatically at SD startup whenever DMSG is enabled; the API calls into it via SetServicesCXOPublisher.

func StartServicesCXOPublisher

func StartServicesCXOPublisher(_ context.Context, dmsgC *dmsg.Client, sk cipher.SecKey, logger logrus.FieldLogger) (*ServicesCXOPublisher, error)

StartServicesCXOPublisher constructs a publisher backed by the given DMSG client and SD secret key. The publisher's allowlist is open — the underlying SD HTTP routes are public reads, so the CXO mirror inherits that. Returns nil + error if the publisher can't be created (no dmsg client, listener bind failure, etc.); callers log and continue without it (the HTTP path remains the source of truth).

func (*ServicesCXOPublisher) Close

func (p *ServicesCXOPublisher) Close() error

Close stops the worker goroutine and the underlying publisher. Pending events at the moment of Close are discarded (the underlying CXO state is best-effort — a fresh SD startup will re-emit the live entries on the next register / heartbeat). Safe to call multiple times.

func (*ServicesCXOPublisher) DelEntry

func (p *ServicesCXOPublisher) DelEntry(svcType string, pk cipher.PubKey)

DelEntry mirrors a service deregister or expiry. Removes the service from its type's set and re-encodes that type's batched leaf; the service's absence from the leaf IS the deletion signal (no tombstone leaf — see the package docstring). Best-effort and non-blocking.

func (*ServicesCXOPublisher) Dropped

func (p *ServicesCXOPublisher) Dropped() uint64

Dropped returns the cumulative count of publish operations that were dropped because the queue was full at submit time. Exposed for /health-style introspection and as a tripwire for "subscribers are slow enough that the mirror is degrading".

func (*ServicesCXOPublisher) FeedPK

func (p *ServicesCXOPublisher) FeedPK() cipher.PubKey

FeedPK returns the publisher's feed PK (SD's own PK, since the publisher was constructed with SD's secret key). Subscribers connect to this PK on skyenv.DmsgSDServicesCXOPort.

func (*ServicesCXOPublisher) LastError

func (p *ServicesCXOPublisher) LastError() error

LastError returns the most recent publish error, or nil. Exposed for /health-style introspection.

func (*ServicesCXOPublisher) Publisher added in v1.3.98

func (x *ServicesCXOPublisher) Publisher() *treestore.Publisher

Publisher returns the underlying feed publisher, for introspection.

func (*ServicesCXOPublisher) PutEntry

func (p *ServicesCXOPublisher) PutEntry(svc *servicedisc.Service)

PutEntry mirrors a service register/update. Updates the worker's per-type service state and re-encodes that type's batched leaf — but only when the service's materially-visible content changed (heartbeat short-circuit), so a steady re-register stream does not churn the tree. Best-effort and non-blocking: queued for the publish worker; dropped on queue overflow.

Jump to

Keyboard shortcuts

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