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 ¶
- type ContentWriter
- type Coordinator
- type DHT
- type ErrNotFound
- type ErrPeerHTTPStatus
- type ErrUnavailable
- type FailureClass
- type LocalContentStore
- type LocalIntentProvider
- type LocalPullStarter
- type Members
- type Node
- type NodeID
- type OriginError
- type OriginPuller
- type OriginRef
- type OriginRefKind
- type PeerDialer
- type PleasePullOutcome
- type PleasePullStatus
- type Provider
- type PullIntent
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 ¶
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 ¶
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 {
}
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.