refactor: container status constants

This commit is contained in:
Rob Watson 2025-03-05 04:51:06 +01:00
parent f5bfa62330
commit e3ca34e8e0
8 changed files with 45 additions and 33 deletions

2
.gitignore vendored
View File

@ -1,2 +1,2 @@
/octoplex.log *.log
/mediamtx.yml /mediamtx.yml

View File

@ -185,7 +185,7 @@ func (a *Client) RunContainer(ctx context.Context, params RunContainerParams) (<
sendError(fmt.Errorf("container create: %w", err)) sendError(fmt.Errorf("container create: %w", err))
return return
} }
containerStateC <- domain.Container{ID: createResp.ID, State: "created"} containerStateC <- domain.Container{ID: createResp.ID, Status: domain.ContainerStatusCreated}
if err = a.apiClient.NetworkConnect(ctx, a.networkID, createResp.ID, nil); err != nil { if err = a.apiClient.NetworkConnect(ctx, a.networkID, createResp.ID, nil); err != nil {
sendError(fmt.Errorf("network connect: %w", err)) sendError(fmt.Errorf("network connect: %w", err))
@ -198,7 +198,7 @@ func (a *Client) RunContainer(ctx context.Context, params RunContainerParams) (<
} }
a.logger.Info("Started container", "id", shortID(createResp.ID), "duration", time.Since(now)) a.logger.Info("Started container", "id", shortID(createResp.ID), "duration", time.Since(now))
containerStateC <- domain.Container{ID: createResp.ID, State: "running"} containerStateC <- domain.Container{ID: createResp.ID, Status: domain.ContainerStatusRunning}
a.runContainerLoop(ctx, createResp.ID, params.NetworkCountConfig, containerStateC, errC) a.runContainerLoop(ctx, createResp.ID, params.NetworkCountConfig, containerStateC, errC)
}() }()
@ -216,7 +216,7 @@ func (a *Client) pullImageIfNeeded(ctx context.Context, imageName string, contai
return nil return nil
} }
containerStateC <- domain.Container{State: "pulling"} containerStateC <- domain.Container{Status: domain.ContainerStatusPulling}
pullReader, err := a.apiClient.ImagePull(ctx, imageName, image.PullOptions{}) pullReader, err := a.apiClient.ImagePull(ctx, imageName, image.PullOptions{})
if err != nil { if err != nil {
@ -292,7 +292,7 @@ func (a *Client) runContainerLoop(
} }
}() }()
state := &domain.Container{ID: containerID, State: "running"} state := &domain.Container{ID: containerID, Status: domain.ContainerStatusRunning}
sendState := func() { stateC <- *state } sendState := func() { stateC <- *state }
sendState() sendState()
@ -308,7 +308,7 @@ func (a *Client) runContainerLoop(
containerState = "exited" containerState = "exited"
} }
state.State = containerState state.Status = containerState
state.CPUPercent = 0 state.CPUPercent = 0
state.MemoryUsageBytes = 0 state.MemoryUsageBytes = 0
state.HealthState = "unhealthy" state.HealthState = "unhealthy"
@ -338,7 +338,7 @@ func (a *Client) runContainerLoop(
} }
if evt.Action == "start" { if evt.Action == "start" {
state.State = "running" state.Status = domain.ContainerStatusRunning
sendState() sendState()
continue continue
} }

View File

@ -89,10 +89,10 @@ func TestClientRunContainer(t *testing.T) {
require.NoError(t, <-errC) require.NoError(t, <-errC)
}() }()
assert.Equal(t, "pulling", (<-containerStateC).State) assert.Equal(t, "pulling", (<-containerStateC).Status)
assert.Equal(t, "created", (<-containerStateC).State) assert.Equal(t, "created", (<-containerStateC).Status)
assert.Equal(t, "running", (<-containerStateC).State) assert.Equal(t, "running", (<-containerStateC).Status)
assert.Equal(t, "running", (<-containerStateC).State) assert.Equal(t, "running", (<-containerStateC).Status)
// Enough time for events channel to receive a message: // Enough time for events channel to receive a message:
time.Sleep(100 * time.Millisecond) time.Sleep(100 * time.Millisecond)
@ -100,7 +100,7 @@ func TestClientRunContainer(t *testing.T) {
containerWaitC <- dockercontainer.WaitResponse{StatusCode: 1} containerWaitC <- dockercontainer.WaitResponse{StatusCode: 1}
state := <-containerStateC state := <-containerStateC
assert.Equal(t, "exited", state.State) assert.Equal(t, "exited", state.Status)
assert.Equal(t, "unhealthy", state.HealthState) assert.Equal(t, "unhealthy", state.HealthState)
require.NotNil(t, state.ExitCode) require.NotNil(t, state.ExitCode)
assert.Equal(t, 1, *state.ExitCode) assert.Equal(t, 1, *state.ExitCode)
@ -146,8 +146,8 @@ func TestClientRunContainerErrorStartingContainer(t *testing.T) {
HostConfig: &dockercontainer.HostConfig{}, HostConfig: &dockercontainer.HostConfig{},
}) })
assert.Equal(t, "pulling", (<-containerStateC).State) assert.Equal(t, "pulling", (<-containerStateC).Status)
assert.Equal(t, "created", (<-containerStateC).State) assert.Equal(t, "created", (<-containerStateC).Status)
err = <-errC err = <-errC
require.EqualError(t, err, "container start: error starting container") require.EqualError(t, err, "container start: error starting container")

View File

@ -9,6 +9,7 @@ import (
"time" "time"
"git.netflux.io/rob/octoplex/container" "git.netflux.io/rob/octoplex/container"
"git.netflux.io/rob/octoplex/domain"
"git.netflux.io/rob/octoplex/shortid" "git.netflux.io/rob/octoplex/shortid"
"git.netflux.io/rob/octoplex/testhelpers" "git.netflux.io/rob/octoplex/testhelpers"
typescontainer "github.com/docker/docker/api/types/container" typescontainer "github.com/docker/docker/api/types/container"
@ -196,11 +197,11 @@ func TestContainerRestart(t *testing.T) {
testhelpers.ChanRequireNoError(t, errC) testhelpers.ChanRequireNoError(t, errC)
containerState := <-containerStateC containerState := <-containerStateC
assert.Equal(t, "pulling", containerState.State) assert.Equal(t, "pulling", containerState.Status)
containerState = <-containerStateC containerState = <-containerStateC
assert.Equal(t, "created", containerState.State) assert.Equal(t, "created", containerState.Status)
containerState = <-containerStateC containerState = <-containerStateC
assert.Equal(t, "running", containerState.State) assert.Equal(t, "running", containerState.Status)
err = nil // reset error err = nil // reset error
done := make(chan struct{}) done := make(chan struct{})
@ -210,9 +211,9 @@ func TestContainerRestart(t *testing.T) {
var count int var count int
for { for {
containerState = <-containerStateC containerState = <-containerStateC
if containerState.State == "restarting" { if containerState.Status == domain.ContainerStatusRestarting {
break break
} else if containerState.State == "exited" { } else if containerState.Status == domain.ContainerStatusExited {
err = errors.New("container exited unexpectedly") err = errors.New("container exited unexpectedly")
} else if count >= 5 { } else if count >= 5 {
err = errors.New("container did not enter restarting state") err = errors.New("container did not enter restarting state")

View File

@ -55,13 +55,24 @@ type Destination struct {
URL string URL string
} }
// Container represents the current state of an individual container. // Container status strings.
// //
// The source of truth is always the Docker daemon, this struct is used only // TODO: refactor to strictly reflect Docker status strings.
// for passing asynchronous state. const (
ContainerStatusPulling = "pulling" // Does not correspond to a Docker status.
ContainerStatusCreated = "created"
ContainerStatusRunning = "running"
ContainerStatusPaused = "paused"
ContainerStatusRestarting = "restarting"
ContainerStatusRemoving = "removing"
ContainerStatusExited = "exited"
ContainerStatusDead = "dead"
)
// Container represents the current state of an individual container.
type Container struct { type Container struct {
ID string ID string
State string Status string
HealthState string HealthState string
CPUPercent float64 CPUPercent float64
MemoryUsageBytes uint64 MemoryUsageBytes uint64

View File

@ -172,7 +172,7 @@ func (s *Actor) actorLoop(containerStateC <-chan domain.Container, errC <-chan e
case containerState := <-containerStateC: case containerState := <-containerStateC:
s.state.Container = containerState s.state.Container = containerState
if s.state.Container.State == "exited" { if s.state.Container.Status == domain.ContainerStatusExited {
fetchStateT.Stop() fetchStateT.Stop()
s.handleContainerExit(nil) s.handleContainerExit(nil)
} }

View File

@ -146,7 +146,7 @@ func (a *Actor) destLoop(url string, containerStateC <-chan domain.Container, er
case containerState := <-containerStateC: case containerState := <-containerStateC:
state.Container = containerState state.Container = containerState
if containerState.State == "running" { if containerState.Status == "running" {
if hasElapsedSince(5*time.Second, containerState.RxSince) { if hasElapsedSince(5*time.Second, containerState.RxSince) {
state.Status = domain.DestinationStatusLive state.Status = domain.DestinationStatusLive
} else { } else {

View File

@ -326,7 +326,7 @@ func (ui *UI) redrawFromState(state domain.AppState) {
} }
ui.sourceViews.status.SetText("[black:green]receiving" + durStr) ui.sourceViews.status.SetText("[black:green]receiving" + durStr)
} else if state.Source.Container.State == "running" && state.Source.Container.HealthState == "healthy" { } else if state.Source.Container.Status == domain.ContainerStatusRunning && state.Source.Container.HealthState == "healthy" {
ui.sourceViews.status.SetText("[black:yellow]ready") ui.sourceViews.status.SetText("[black:yellow]ready")
} else { } else {
ui.sourceViews.status.SetText("[white:red]not ready") ui.sourceViews.status.SetText("[white:red]not ready")
@ -335,19 +335,19 @@ func (ui *UI) redrawFromState(state domain.AppState) {
ui.sourceViews.health.SetText("[white]" + cmp.Or(rightPad(state.Source.Container.HealthState, 9), dash)) ui.sourceViews.health.SetText("[white]" + cmp.Or(rightPad(state.Source.Container.HealthState, 9), dash))
cpuPercent := dash cpuPercent := dash
if state.Source.Container.State == "running" { if state.Source.Container.Status == domain.ContainerStatusRunning {
cpuPercent = fmt.Sprintf("%.1f", state.Source.Container.CPUPercent) cpuPercent = fmt.Sprintf("%.1f", state.Source.Container.CPUPercent)
} }
ui.sourceViews.cpu.SetText("[white]" + cpuPercent) ui.sourceViews.cpu.SetText("[white]" + cpuPercent)
memUsage := dash memUsage := dash
if state.Source.Container.State == "running" { if state.Source.Container.Status == domain.ContainerStatusRunning {
memUsage = fmt.Sprintf("%.1f", float64(state.Source.Container.MemoryUsageBytes)/1024/1024) memUsage = fmt.Sprintf("%.1f", float64(state.Source.Container.MemoryUsageBytes)/1024/1024)
} }
ui.sourceViews.mem.SetText("[white]" + memUsage) ui.sourceViews.mem.SetText("[white]" + memUsage)
rxRate := dash rxRate := dash
if state.Source.Container.State == "running" { if state.Source.Container.Status == domain.ContainerStatusRunning {
rxRate = fmt.Sprintf("%d", state.Source.Container.RxRate) rxRate = fmt.Sprintf("%d", state.Source.Container.RxRate)
} }
ui.sourceViews.rx.SetText("[white]" + rxRate) ui.sourceViews.rx.SetText("[white]" + rxRate)
@ -385,7 +385,7 @@ func (ui *UI) redrawFromState(state domain.AppState) {
ui.destView.SetCell(i+1, 2, tview.NewTableCell("[white]"+rightPad("off-air", statusLen))) ui.destView.SetCell(i+1, 2, tview.NewTableCell("[white]"+rightPad("off-air", statusLen)))
} }
ui.destView.SetCell(i+1, 3, tview.NewTableCell("[white]"+rightPad(cmp.Or(dest.Container.State, dash), 10))) ui.destView.SetCell(i+1, 3, tview.NewTableCell("[white]"+rightPad(cmp.Or(dest.Container.Status, dash), 10)))
healthState := dash healthState := dash
if dest.Status == domain.DestinationStatusLive { if dest.Status == domain.DestinationStatusLive {
@ -394,19 +394,19 @@ func (ui *UI) redrawFromState(state domain.AppState) {
ui.destView.SetCell(i+1, 4, tview.NewTableCell("[white]"+rightPad(healthState, 7))) ui.destView.SetCell(i+1, 4, tview.NewTableCell("[white]"+rightPad(healthState, 7)))
cpuPercent := dash cpuPercent := dash
if dest.Container.State == "running" { if dest.Container.Status == domain.ContainerStatusRunning {
cpuPercent = fmt.Sprintf("%.1f", dest.Container.CPUPercent) cpuPercent = fmt.Sprintf("%.1f", dest.Container.CPUPercent)
} }
ui.destView.SetCell(i+1, 5, tview.NewTableCell("[white]"+rightPad(cpuPercent, 4))) ui.destView.SetCell(i+1, 5, tview.NewTableCell("[white]"+rightPad(cpuPercent, 4)))
memoryUsage := dash memoryUsage := dash
if dest.Container.State == "running" { if dest.Container.Status == domain.ContainerStatusRunning {
memoryUsage = fmt.Sprintf("%.1f", float64(dest.Container.MemoryUsageBytes)/1000/1000) memoryUsage = fmt.Sprintf("%.1f", float64(dest.Container.MemoryUsageBytes)/1000/1000)
} }
ui.destView.SetCell(i+1, 6, tview.NewTableCell("[white]"+rightPad(memoryUsage, 4))) ui.destView.SetCell(i+1, 6, tview.NewTableCell("[white]"+rightPad(memoryUsage, 4)))
txRate := dash txRate := dash
if dest.Container.State == "running" { if dest.Container.Status == domain.ContainerStatusRunning {
txRate = "[white]" + rightPad(strconv.Itoa(dest.Container.TxRate), 4) txRate = "[white]" + rightPad(strconv.Itoa(dest.Container.TxRate), 4)
} }
ui.destView.SetCell(i+1, 7, tview.NewTableCell(txRate)) ui.destView.SetCell(i+1, 7, tview.NewTableCell(txRate))