ifaces

package
v0.1.21 Latest Latest
Warning

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

Go to latest
Published: Jul 7, 2026 License: MIT Imports: 5 Imported by: 0

Documentation

Overview

Package ifaces declares the cross-cutting interfaces that Gantry's subsystems implement and depend on.

Each subsystem (cache, members, origin, peer, DHT) is reachable through the interfaces defined here so that:

- Unit tests can replace any subsystem with a fake (see internal/ifaces/fakes). - The top-level agent wiring in internal/agent depends only on interfaces, not on concrete libp2p / Kubernetes / hostPath implementations.

Interfaces are intentionally minimal - only the methods the agent actually uses are exposed. Adding a method here should follow real demand from a caller, not speculative API surface.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type ContentWriter

type ContentWriter interface {
	io.Writer

	// Commit finalizes the entry. Fails if the streamed bytes did not hash
	// to the declared digest.
	Commit(ctx context.Context) error

	// Abort discards staged bytes. Idempotent.
	Abort(ctx context.Context) error
}

ContentWriter accumulates bytes for a single LocalContentStore entry. The implementation computes the digest incrementally; Commit fails if the streamed bytes do not match the digest passed to LocalContentStore.Writer.

type Coordinator

type Coordinator interface {
	PullIntentQuery(ctx context.Context, peer NodeID, d digest.Digest) (PullIntent, error)
	PleasePull(ctx context.Context, peer NodeID, registry, repository string, kind OriginRefKind, digests []digest.Digest) ([]PleasePullOutcome, error)
}

Coordinator issues coordination RPCs to peers. Implementations are expected to open one libp2p stream per call.

type DHT

type DHT interface {
	// FindProviders returns providers of d. Returning an empty slice and a
	// nil error is the "DHT-empty" case (the design doc): the caller MUST NOT treat
	// it as ground truth and SHOULD fall through to the HRW top-K probe.
	FindProviders(ctx context.Context, d digest.Digest) ([]Provider, error)

	// Provide advertises that this node holds d. Idempotent at the DHT
	// level; refreshing is the implementation's responsibility (libp2p
	// default 12 h refresh, 24 h TTL - the design doc).
	Provide(ctx context.Context, d digest.Digest) error

	// Withdraw is a soft "stop advertising" hint sent by the advertiser
	// when the digest is no longer present in the local content store
	// (e.g. containerd GC'd it). libp2p has no protocol-level withdraw
	// - existing provider records expire at the 24 h TTL - so this is
	// implementation-defined cooperation: at minimum the local agent
	// MUST stop re-Providing the digest on the next refresh cycle so
	// the stale record drains naturally. Returning a non-nil error
	// signals the advertiser to retry; nil means "acknowledged, do not
	// re-announce".
	Withdraw(ctx context.Context, d digest.Digest) error

	// Health returns the current DHT health score in [0,1] as defined by
	// the design doc (geometric mean of routing-table coverage, lookup-latency
	// score, and self-test success rate).
	Health() float64
}

DHT exposes the libp2p Kademlia operations Gantry needs.

type ErrNotFound

type ErrNotFound struct {
	Digest digest.Digest
}

ErrNotFound is returned by Cache and PeerDialer to signal a digest is not locally available. Distinct from transport-level errors so callers can distinguish "fall back to next provider" from "definitively missing here".

func (*ErrNotFound) Error

func (e *ErrNotFound) Error() string

type ErrPeerHTTPStatus

type ErrPeerHTTPStatus struct {
	PeerAddr   string
	StatusCode int
}

ErrPeerHTTPStatus is returned by PeerDialer implementations when a peer transfer endpoint responds with an unexpected HTTP status. Callers can use StatusCode to classify failures (auth/config, server error, protocol error) without parsing error strings.

func (*ErrPeerHTTPStatus) Error

func (e *ErrPeerHTTPStatus) Error() string

type ErrUnavailable

type ErrUnavailable struct {
	Op    string
	Cause error
}

ErrUnavailable signals that the local storage backend (typically containerd) is currently unreachable or otherwise unable to answer presence/open requests. Per callers MUST distinguish this from ErrNotFound:

- mirror/coord must NOT report "cache miss" (which would trigger DHT lookup + cold-start + origin fallback on data that may still be on-node); - transfer must respond HTTP 503 (not 404), so a peer treats this node as temporarily down rather than as definitive proof the digest is absent; - advertise must pause Withdraw on an Inventory failure, since an empty inventory caused by an unavailable backend would otherwise look like "everything was evicted".

Cause is the underlying error (typically a gRPC Unavailable, a connection refused, or a context.DeadlineExceeded). Op is a short operation name ("Info"/"ReaderAt"/"Writer"/"Walk") for log correlation.

func (*ErrUnavailable) Error

func (e *ErrUnavailable) Error() string

func (*ErrUnavailable) Unwrap

func (e *ErrUnavailable) Unwrap() error

type FailureClass

type FailureClass string

FailureClass mirrors coordv1.FailureClass; defined here so non-proto callers don't import the generated package.

const (
	FailureUnspecified FailureClass = ""
	FailureAuth        FailureClass = "auth"
	FailureNotFound    FailureClass = "not_found"
	FailureRateLimited FailureClass = "rate_limited"
	FailureTransient   FailureClass = "transient"
)

Recognised the design doc failure classifications.

type LocalContentStore

type LocalContentStore interface {
	// Has reports whether the digest is present in the local store.
	Has(ctx context.Context, d digest.Digest) (bool, error)

	// Open returns a reader for the stored bytes plus the content
	// length. Returns ErrNotFound if absent.
	Open(ctx context.Context, d digest.Digest) (io.ReadCloser, int64, error)

	// Writer returns a digest-verifying writer for d. Bytes written
	// are staged; the entry becomes visible to subsequent Has/Open
	// calls only after Commit. Abort discards the staging area.
	Writer(ctx context.Context, d digest.Digest) (ContentWriter, error)
}

LocalContentStore is a content-addressed store keyed by OCI digest. Implementations MUST verify the streamed bytes against the digest before treating an entry as committed (digest-verification in architecture.md).

In production this is backed by the local containerd content store (internal/containerdstore.Store) - the same store kubelet and CRI already read from / write to. There is no separate Gantry-owned blob cache.

type LocalIntentProvider

type LocalIntentProvider interface {
	LocalPullIntent(ctx context.Context, d digest.Digest) PullIntent
}

LocalIntentProvider computes the PullIntent for self synchronously, without going through a libp2p coord stream. The cold-start orchestrator uses it to include self as a first-class participant in the rule cascade so that when self is HRW rank 0, self pulls instead of delegating to rank 1 (which violates the "one origin pull per digest" thundering-herd invariant - every requester must converge on the same designated puller, and that puller MAY be self).

type LocalPullStarter

type LocalPullStarter interface {
	StartLocalPull(ctx context.Context, registry, repository string, kind OriginRefKind, digests []digest.Digest) ([]PleasePullOutcome, error)
}

LocalPullStarter starts an origin pull on the local node without going through a libp2p coord stream. The cold-start orchestrator invokes this when rule 7 selects self as the designated puller; the wire-level alternative (Coord.PleasePull(self, ...)) would either fail to dial self or - worse - round-trip through libp2p and burn a stream slot. Semantics MUST match the server-side please_pull handler: each digest either starts a new origin pull, piggybacks on an already-in-flight one (PleasePullAlreadyPulling), or short-circuits on the negative cache (PleasePullRecentlyFailed).

type Members

type Members interface {
	// Self returns this agent's own NodeID.
	Self() NodeID

	// Snapshot returns the current node list. The returned slice is owned
	// by the caller; implementations MUST copy if they retain it.
	Snapshot() []Node

	// WaitForSync blocks until the underlying informer has completed its
	// initial list-and-watch sync. Used by readiness probes.
	WaitForSync(ctx context.Context) error
}

Members is the live cluster-membership view.

type Node

type Node struct {
	ID NodeID

	// Addr is the network address to reach this node's transfer
	// endpoint (HTTP/2 on the configured transfer port). When the
	// transfer port is known (production deploy), Addr is "ip:port";
	// for back-compat with older snapshots it may be a bare IP and
	// callers must append the port.
	Addr string

	// Zone is the optional topology label `topology.kubernetes.io/zone`.
	// Empty when not topology-aware (the design doc).
	Zone string

	// PeerID is the libp2p peer.ID (CID-encoded string form) the node
	// publishes via its pod annotation. Empty until the peer announces.
	// coord.Client uses this to dial via libp2p without requiring that
	// NodeID itself be a peer.ID string.
	PeerID string

	// P2PAddrs lists the node's libp2p listen multiaddrs published via
	// pod annotation. Empty until the peer announces. main.go reads
	// this on startup to seed disco.Connect for DHT bootstrap (the design doc)
	// without needing operator-supplied bootstrap_peers.
	P2PAddrs []string
}

Node is one entry in the cluster-membership view.

type NodeID

type NodeID string

NodeID is the stable identity used by HRW (the step 3) - typically the pod or node name. It MUST be stable across an individual node's lifetime and identical across all agents' views (modulo informer lag, the design doc).

type OriginError

type OriginError struct {
	Ref   OriginRef
	Class FailureClass
	Err   error
}

OriginError is the error returned by OriginPuller.Pull for terminal failures. The Class field is the classification used by the negative cache and propagated via PullIntentResponse.failure_class.

func (*OriginError) Error

func (e *OriginError) Error() string

func (*OriginError) Unwrap

func (e *OriginError) Unwrap() error

type OriginPuller

type OriginPuller interface {
	// Pull opens a streaming read of the digest's bytes from origin. The
	// returned ReadCloser is digest-unverified; the caller is expected to
	// verify via a Cache writer or equivalent.
	//
	// On terminal failure the returned error is wrapped in an *OriginError
	// carrying the failure classification used by the design doc.
	Pull(ctx context.Context, ref OriginRef) (io.ReadCloser, int64, error)

	// Head fetches metadata for a digest without transferring the body.
	// Used by the mirror's HEAD handler to satisfy distribution-spec
	// metadata requests on a cache miss without doing a full origin pull.
	//
	// Head is deliberately NOT counted in p2p_origin_pull_total or its
	// success/failure siblings. Those counters describe byte-pull
	// attempts: metadata-only HEAD is a different operation class, and
	// folding HEAD calls into pull totals broke the per-pull arithmetic
	// (started == success + failure + in_flight) because HEAD never
	// produces bytes, never commits to cache, and therefore can fire
	// neither success nor downstream-failure. See origin.Client.Head's
	// implementation comment for why HEAD failures also stay out of
	// p2p_origin_pull_failure_total.
	//
	// The returned contentType is the upstream registry's Content-Type
	// header verbatim (may be "" if the upstream omitted it). The mirror
	// needs this so that HEAD responses to containerd carry the same
	// media type the eventual GET will. Without it containerd builds a
	// descriptor with the wrong type at HEAD time and later fails the
	// unpack with "expected manifest but found index" when the body is
	// a manifest list (see mirror.writeBlobHeadersWithPrefix).
	//
	// On terminal failure the returned error is wrapped in an
	// *OriginError carrying the failure classification used by the design doc so
	// the mirror can convert it to the right HTTP status.
	Head(ctx context.Context, ref OriginRef) (size int64, contentType string, err error)
}

OriginPuller fetches a single digest from origin.

type OriginRef

type OriginRef struct {
	Registry   string // e.g. "registry.example.com"
	Repository string // e.g. "library/nginx"
	Digest     digest.Digest

	// Kind discriminates the OCI Distribution Spec URL family for this
	// reference. Manifests live at /v2/<repo>/manifests/<digest>, blobs at
	// /v2/<repo>/blobs/<digest>. Zero value (KindBlob) is the common case;
	// only the mirror's manifest-by-digest path and cold-start manifest
	// pulls set KindManifest.
	Kind OriginRefKind
}

OriginRef identifies a digest at a specific upstream registry / repository. The triple matches the fields of coordv1.PleasePullRequest.

type OriginRefKind

type OriginRefKind int

OriginRefKind discriminates manifest vs blob URLs at the upstream.

Note on KindConfig: per the OCI Distribution Spec the image-config document is fetched from /v2/<repo>/blobs/<digest> - the same URL family as KindBlob. KindConfig therefore does NOT change routing; it exists purely to tighten metric/log labels so cold-start manifest -> config -> blob traversal is distinguishable from regular layer fetches when reading dashboards or traces.

const (
	KindBlob     OriginRefKind = 0
	KindManifest OriginRefKind = 1
	KindConfig   OriginRefKind = 2
)

Recognised OriginRefKind values.

func (OriginRefKind) MetricLabel

func (k OriginRefKind) MetricLabel() string

MetricLabel returns the Prometheus label vocabulary the design doc commits to for the p2p_origin_pull_total / _success_total / _failure_total counters:

p2p_origin_pull_total{kind="manifest|config|layer"}

MetricLabel is intentionally distinct from String: String returns "blob" for KindBlob (the OCI Distribution Spec URL-family term, correct in logs and on the wire) while MetricLabel returns "layer" (the operator-facing observability term, what dashboards built against the design spec expect). The two roles must not be conflated - leaking "blob" into Prometheus labels gives dashboards an empty "layer" bucket plus an undocumented "blob" series.

KindConfig is preserved as "config" so the per-kind counter distinguishes the single image-config blob per manifest from the many layer blobs. This is the load-bearing observability invariant the work plumbed end-to-end through manifest.TypedChildren -> coldstart.PrefetchChildren -> please_pull proto KIND_CONFIG; this method is the leaf node of that chain.

func (OriginRefKind) String

func (k OriginRefKind) String() string

type PeerDialer

type PeerDialer interface {
	// FetchFromPeer streams the digest's bytes from peerAddr's :5001
	// endpoint. The implementation MUST set `Gantry-Mirrored: 1` and MUST
	// surface a NotFound error distinctly from transport errors so the
	// caller can fail over to the next provider.
	FetchFromPeer(ctx context.Context, peerAddr string, ref OriginRef) (io.ReadCloser, int64, error)
}

PeerDialer fetches a digest from a peer's transfer endpoint with the `Gantry-Mirrored: 1` header set (architecture.md the API contract).

type PleasePullOutcome

type PleasePullOutcome struct {
	Digest        digest.Digest
	Outcome       PleasePullStatus
	StartedAt     time.Time
	CooldownUntil time.Time
	FailureClass  FailureClass
}

PleasePullOutcome is the requester-side view of a single PleasePullResponse.Result.

type PleasePullStatus

type PleasePullStatus int

PleasePullStatus mirrors coordv1.PleasePullResponse.Result.Outcome.

const (
	PleasePullUnspecified PleasePullStatus = iota
	PleasePullAlreadyPulling
	PleasePullStarted
	PleasePullRecentlyFailed
)

Recognised PleasePull outcome values.

type Provider

type Provider struct {
	NodeID NodeID
	Addr   string
}

Provider is one entry returned by DHT.FindProviders.

type PullIntent

type PullIntent struct {
	HasCached      bool
	InFlight       bool
	StartedAt      time.Time
	RecipientRank  int32
	RecentlyFailed bool
	CooldownUntil  time.Time
	FailureClass   FailureClass
}

PullIntent is the requester-side view of a PullIntentResponse.

Directories

Path Synopsis
Package fakes provides in-memory implementations of the ifaces interfaces for unit and integration tests.
Package fakes provides in-memory implementations of the ifaces interfaces for unit and integration tests.

Jump to

Keyboard shortcuts

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