feat: runtime manager
This commit is contained in:
@@ -0,0 +1,336 @@
|
||||
package ports
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
// PullPolicy enumerates the supported image pull policies. The value
|
||||
// set mirrors `config.ImagePullPolicy`; the runtime/wiring layer
|
||||
// translates between the two so the docker adapter does not import
|
||||
// `internal/config` and the port package stays free of configuration
|
||||
// concerns.
|
||||
type PullPolicy string
|
||||
|
||||
// Supported pull policies, frozen by `rtmanager/README.md §Configuration`.
|
||||
const (
|
||||
// PullPolicyIfMissing pulls the image only when it is absent from
|
||||
// the local Docker daemon.
|
||||
PullPolicyIfMissing PullPolicy = "if_missing"
|
||||
|
||||
// PullPolicyAlways pulls the image on every start.
|
||||
PullPolicyAlways PullPolicy = "always"
|
||||
|
||||
// PullPolicyNever skips the pull and fails the start when the image
|
||||
// is absent.
|
||||
PullPolicyNever PullPolicy = "never"
|
||||
)
|
||||
|
||||
// IsKnown reports whether policy belongs to the frozen pull-policy
|
||||
// vocabulary.
|
||||
func (policy PullPolicy) IsKnown() bool {
|
||||
switch policy {
|
||||
case PullPolicyIfMissing, PullPolicyAlways, PullPolicyNever:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
//go:generate go run go.uber.org/mock/mockgen -destination=../adapters/docker/mocks/mock_dockerclient.go -package=mocks galaxy/rtmanager/internal/ports DockerClient
|
||||
|
||||
// DockerClient is the narrow Docker port Runtime Manager uses. The
|
||||
// production adapter wraps `github.com/docker/docker/client`; service
|
||||
// tests use a generated mock. The surface intentionally exposes only
|
||||
// the operations RTM needs; `docker logs` and stream attach are out
|
||||
// of scope for v1.
|
||||
type DockerClient interface {
|
||||
// EnsureNetwork verifies the configured Docker network is present
|
||||
// on the daemon. It returns ErrNetworkMissing when the network does
|
||||
// not exist; RTM never creates networks itself.
|
||||
EnsureNetwork(ctx context.Context, name string) error
|
||||
|
||||
// PullImage pulls ref according to policy. It returns nil on
|
||||
// success and a wrapped Docker error otherwise. Implementations
|
||||
// honour PullPolicyNever by skipping the pull and returning nil
|
||||
// when the image is already present, or returning ErrImageNotFound
|
||||
// otherwise.
|
||||
PullImage(ctx context.Context, ref string, policy PullPolicy) error
|
||||
|
||||
// InspectImage returns image metadata for ref. It returns
|
||||
// ErrImageNotFound when no such image exists locally.
|
||||
InspectImage(ctx context.Context, ref string) (ImageInspect, error)
|
||||
|
||||
// InspectContainer returns container metadata for containerID. It
|
||||
// returns ErrContainerNotFound when no such container exists.
|
||||
InspectContainer(ctx context.Context, containerID string) (ContainerInspect, error)
|
||||
|
||||
// Run creates and starts one container according to spec. The
|
||||
// returned RunResult carries the assigned container id, the stable
|
||||
// engine endpoint, and the wall-clock observed by the daemon.
|
||||
Run(ctx context.Context, spec RunSpec) (RunResult, error)
|
||||
|
||||
// Stop sends SIGTERM to the container followed by SIGKILL after
|
||||
// timeout. It returns nil when the container exited cleanly and
|
||||
// ErrContainerNotFound when it is already gone.
|
||||
Stop(ctx context.Context, containerID string, timeout time.Duration) error
|
||||
|
||||
// Remove removes the container. It returns nil when the container
|
||||
// no longer exists (idempotent removal).
|
||||
Remove(ctx context.Context, containerID string) error
|
||||
|
||||
// List returns container summaries that match filter. Implementations
|
||||
// translate ListFilter into the appropriate Docker filters argument.
|
||||
List(ctx context.Context, filter ListFilter) ([]ContainerSummary, error)
|
||||
|
||||
// EventsListen subscribes to the Docker events stream and returns
|
||||
// the decoded event channel together with an asynchronous error
|
||||
// channel. The caller cancels ctx to terminate the subscription.
|
||||
// Implementations close events when the subscription terminates.
|
||||
EventsListen(ctx context.Context) (events <-chan DockerEvent, errs <-chan error, err error)
|
||||
}
|
||||
|
||||
// RunSpec stores the request shape used by DockerClient.Run.
|
||||
type RunSpec struct {
|
||||
// Name stores the container name (typically `galaxy-game-{game_id}`).
|
||||
Name string
|
||||
|
||||
// Image stores the image reference resolved by the producer.
|
||||
Image string
|
||||
|
||||
// Hostname stores the container hostname assigned for the embedded
|
||||
// Docker DNS to resolve from other containers on the network.
|
||||
Hostname string
|
||||
|
||||
// Network stores the user-defined Docker network the container
|
||||
// attaches to.
|
||||
Network string
|
||||
|
||||
// Env stores the environment variables forwarded to the container
|
||||
// (e.g. GAME_STATE_PATH, STORAGE_PATH).
|
||||
Env map[string]string
|
||||
|
||||
// Cmd overrides the entrypoint arguments for the container. Production
|
||||
// callers leave it nil so the engine image's own CMD runs; tests use
|
||||
// it to drive a tiny container that does not embed RTM-specific
|
||||
// behaviour. Empty Cmd means "use image default", which mirrors the
|
||||
// Docker SDK contract.
|
||||
Cmd []string
|
||||
|
||||
// Labels stores the labels applied to the container so the
|
||||
// reconciler and the events listener can identify it.
|
||||
Labels map[string]string
|
||||
|
||||
// BindMounts stores the host-to-container bind mounts. RTM uses
|
||||
// exactly one mount in v1 (the per-game state directory).
|
||||
BindMounts []BindMount
|
||||
|
||||
// LogDriver stores the Docker logging driver name.
|
||||
LogDriver string
|
||||
|
||||
// LogOpts stores the logging-driver options as key=value pairs.
|
||||
LogOpts map[string]string
|
||||
|
||||
// CPUQuota stores the `--cpus` value applied as a resource limit.
|
||||
CPUQuota float64
|
||||
|
||||
// Memory stores the `--memory` value (e.g. `512m`) applied as a
|
||||
// resource limit.
|
||||
Memory string
|
||||
|
||||
// PIDsLimit stores the `--pids-limit` value.
|
||||
PIDsLimit int
|
||||
}
|
||||
|
||||
// BindMount stores one host-to-container bind mount.
|
||||
type BindMount struct {
|
||||
// HostPath stores the absolute host path bound into the container.
|
||||
HostPath string
|
||||
|
||||
// MountPath stores the absolute in-container path the host
|
||||
// directory is mounted at.
|
||||
MountPath string
|
||||
|
||||
// ReadOnly mounts the host path read-only when true.
|
||||
ReadOnly bool
|
||||
}
|
||||
|
||||
// RunResult stores the response shape returned by DockerClient.Run.
|
||||
type RunResult struct {
|
||||
// ContainerID identifies the created container.
|
||||
ContainerID string
|
||||
|
||||
// EngineEndpoint stores the stable URL Game Master uses to reach
|
||||
// the engine container.
|
||||
EngineEndpoint string
|
||||
|
||||
// StartedAt stores the wall-clock the daemon observed for the
|
||||
// start event.
|
||||
StartedAt time.Time
|
||||
}
|
||||
|
||||
// ImageInspect stores the subset of `docker image inspect` fields RTM
|
||||
// reads. Only Labels are required at start time (resource limits live
|
||||
// there); other fields may be populated when convenient for diagnostics.
|
||||
type ImageInspect struct {
|
||||
// Ref stores the image reference the inspection was scoped to.
|
||||
Ref string
|
||||
|
||||
// Labels stores the image-level labels (e.g.
|
||||
// `com.galaxy.cpu_quota`).
|
||||
Labels map[string]string
|
||||
}
|
||||
|
||||
// ContainerInspect stores the subset of `docker inspect` fields RTM
|
||||
// reads from a running or exited container.
|
||||
type ContainerInspect struct {
|
||||
// ID identifies the container.
|
||||
ID string
|
||||
|
||||
// ImageRef stores the image reference the container was started
|
||||
// from.
|
||||
ImageRef string
|
||||
|
||||
// Hostname stores the container hostname.
|
||||
Hostname string
|
||||
|
||||
// Labels stores the container labels assigned at create time.
|
||||
Labels map[string]string
|
||||
|
||||
// Status stores the verbatim Docker `State.Status` value (e.g.
|
||||
// `running`, `exited`).
|
||||
Status string
|
||||
|
||||
// Health stores the verbatim Docker `State.Health.Status` value
|
||||
// (e.g. `healthy`, `unhealthy`). Empty when the image declares no
|
||||
// HEALTHCHECK.
|
||||
Health string
|
||||
|
||||
// RestartCount stores the Docker `RestartCount` observed at
|
||||
// inspection time.
|
||||
RestartCount int
|
||||
|
||||
// StartedAt stores the daemon-observed start wall-clock.
|
||||
StartedAt time.Time
|
||||
|
||||
// FinishedAt stores the daemon-observed exit wall-clock. Zero when
|
||||
// the container is still running.
|
||||
FinishedAt time.Time
|
||||
|
||||
// ExitCode stores the exit code reported by the daemon. Zero when
|
||||
// the container is still running.
|
||||
ExitCode int
|
||||
|
||||
// OOMKilled reports whether the container was killed by the OOM
|
||||
// killer.
|
||||
OOMKilled bool
|
||||
}
|
||||
|
||||
// ContainerSummary stores the subset of `docker ps` fields RTM reads.
|
||||
type ContainerSummary struct {
|
||||
// ID identifies the container.
|
||||
ID string
|
||||
|
||||
// ImageRef stores the image reference.
|
||||
ImageRef string
|
||||
|
||||
// Hostname stores the container hostname.
|
||||
Hostname string
|
||||
|
||||
// Labels stores the container labels assigned at create time.
|
||||
Labels map[string]string
|
||||
|
||||
// Status stores the verbatim Docker `State.Status` value.
|
||||
Status string
|
||||
|
||||
// StartedAt stores the daemon-observed start wall-clock.
|
||||
StartedAt time.Time
|
||||
}
|
||||
|
||||
// ListFilter stores the criteria used by DockerClient.List.
|
||||
type ListFilter struct {
|
||||
// Labels stores label key=value pairs that must all be present on
|
||||
// the container. Empty matches every container.
|
||||
Labels map[string]string
|
||||
}
|
||||
|
||||
// DockerEvent stores one decoded entry from the Docker events stream.
|
||||
// RTM only consumes container-scoped events.
|
||||
type DockerEvent struct {
|
||||
// Action stores the Docker event action verbatim (e.g. `start`,
|
||||
// `die`, `oom`, `destroy`).
|
||||
Action string
|
||||
|
||||
// ContainerID identifies the container the event refers to.
|
||||
ContainerID string
|
||||
|
||||
// Labels stores the container labels carried by the event
|
||||
// attributes when present.
|
||||
Labels map[string]string
|
||||
|
||||
// ExitCode stores the exit code attribute when applicable (e.g.
|
||||
// `die` events). Zero when the action does not carry one.
|
||||
ExitCode int
|
||||
|
||||
// OccurredAt stores the daemon-observed event wall-clock.
|
||||
OccurredAt time.Time
|
||||
}
|
||||
|
||||
// String returns policy as its stored enum value. Convenient for use in
|
||||
// log fields and error messages.
|
||||
func (policy PullPolicy) String() string {
|
||||
return string(policy)
|
||||
}
|
||||
|
||||
// ErrNetworkMissing reports that the configured Docker network is not
|
||||
// present on the daemon.
|
||||
var ErrNetworkMissing = errors.New("docker network missing")
|
||||
|
||||
// ErrImageNotFound reports that an image reference does not resolve to
|
||||
// a local Docker image.
|
||||
var ErrImageNotFound = errors.New("docker image not found")
|
||||
|
||||
// ErrContainerNotFound reports that a container id does not resolve to
|
||||
// a Docker container.
|
||||
var ErrContainerNotFound = errors.New("docker container not found")
|
||||
|
||||
// Validate reports whether spec carries the structural invariants
|
||||
// required by DockerClient.Run. Adapters use it as the first defence
|
||||
// against malformed specs originating in service code.
|
||||
func (spec RunSpec) Validate() error {
|
||||
if spec.Name == "" {
|
||||
return fmt.Errorf("run spec: name must not be empty")
|
||||
}
|
||||
if spec.Image == "" {
|
||||
return fmt.Errorf("run spec: image must not be empty")
|
||||
}
|
||||
if spec.Hostname == "" {
|
||||
return fmt.Errorf("run spec: hostname must not be empty")
|
||||
}
|
||||
if spec.Network == "" {
|
||||
return fmt.Errorf("run spec: network must not be empty")
|
||||
}
|
||||
if spec.LogDriver == "" {
|
||||
return fmt.Errorf("run spec: log driver must not be empty")
|
||||
}
|
||||
if spec.CPUQuota <= 0 {
|
||||
return fmt.Errorf("run spec: cpu quota must be positive")
|
||||
}
|
||||
if spec.Memory == "" {
|
||||
return fmt.Errorf("run spec: memory must not be empty")
|
||||
}
|
||||
if spec.PIDsLimit <= 0 {
|
||||
return fmt.Errorf("run spec: pids limit must be positive")
|
||||
}
|
||||
for index, mount := range spec.BindMounts {
|
||||
if mount.HostPath == "" {
|
||||
return fmt.Errorf("run spec: bind mounts[%d]: host path must not be empty", index)
|
||||
}
|
||||
if mount.MountPath == "" {
|
||||
return fmt.Errorf("run spec: bind mounts[%d]: mount path must not be empty", index)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user