This test started failing for mysterious reasons; it can be improved by testing our more recent hand-rolled restart logic instead of the previous Docker integration.
236 lines
6.8 KiB
Go
236 lines
6.8 KiB
Go
//go:build integration
|
|
|
|
package container_test
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"testing"
|
|
"time"
|
|
|
|
"git.netflux.io/rob/octoplex/internal/container"
|
|
"git.netflux.io/rob/octoplex/internal/domain"
|
|
"git.netflux.io/rob/octoplex/internal/shortid"
|
|
"git.netflux.io/rob/octoplex/internal/testhelpers"
|
|
typescontainer "github.com/docker/docker/api/types/container"
|
|
"github.com/docker/docker/client"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestIntegrationClientStartStop(t *testing.T) {
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
t.Cleanup(cancel)
|
|
|
|
logger := testhelpers.NewTestLogger(t)
|
|
apiClient, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
|
|
require.NoError(t, err)
|
|
containerName := "octoplex-test-" + shortid.New().String()
|
|
component := "test-start-stop"
|
|
|
|
client, err := container.NewClient(ctx, apiClient, logger)
|
|
require.NoError(t, err)
|
|
|
|
running, err := client.ContainerRunning(ctx, client.ContainersWithLabels(map[string]string{container.LabelComponent: component}))
|
|
require.NoError(t, err)
|
|
assert.False(t, running)
|
|
|
|
containerStateC, errC := client.RunContainer(ctx, container.RunContainerParams{
|
|
Name: containerName,
|
|
ChanSize: 1,
|
|
ContainerConfig: &typescontainer.Config{
|
|
Image: "ghcr.io/rfwatson/mediamtx-alpine:latest",
|
|
Labels: map[string]string{container.LabelComponent: component},
|
|
},
|
|
HostConfig: &typescontainer.HostConfig{
|
|
NetworkMode: "default",
|
|
},
|
|
})
|
|
testhelpers.ChanDiscard(containerStateC)
|
|
testhelpers.ChanRequireNoError(t, errC)
|
|
|
|
require.Eventually(
|
|
t,
|
|
func() bool {
|
|
running, err = client.ContainerRunning(ctx, client.ContainersWithLabels(map[string]string{container.LabelComponent: component}))
|
|
return err == nil && running
|
|
},
|
|
5*time.Second,
|
|
100*time.Millisecond,
|
|
"container not in RUNNING state",
|
|
)
|
|
|
|
client.Close()
|
|
require.NoError(t, <-errC)
|
|
|
|
running, err = client.ContainerRunning(ctx, client.ContainersWithLabels(map[string]string{container.LabelComponent: component}))
|
|
require.NoError(t, err)
|
|
assert.False(t, running)
|
|
}
|
|
|
|
func TestIntegrationClientRemoveContainers(t *testing.T) {
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
t.Cleanup(cancel)
|
|
|
|
logger := testhelpers.NewTestLogger(t)
|
|
apiClient, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
|
|
require.NoError(t, err)
|
|
component := "test-remove-containers"
|
|
|
|
client, err := container.NewClient(ctx, apiClient, logger)
|
|
require.NoError(t, err)
|
|
t.Cleanup(func() { client.Close() })
|
|
|
|
stateC, err1C := client.RunContainer(ctx, container.RunContainerParams{
|
|
ChanSize: 1,
|
|
ContainerConfig: &typescontainer.Config{
|
|
Image: "ghcr.io/rfwatson/mediamtx-alpine:latest",
|
|
Labels: map[string]string{container.LabelComponent: component, "group": "test1"},
|
|
},
|
|
HostConfig: &typescontainer.HostConfig{NetworkMode: "default"},
|
|
})
|
|
require.NoError(t, err)
|
|
testhelpers.ChanDiscard(stateC)
|
|
|
|
stateC, err2C := client.RunContainer(ctx, container.RunContainerParams{
|
|
ChanSize: 1,
|
|
ContainerConfig: &typescontainer.Config{
|
|
Image: "ghcr.io/rfwatson/mediamtx-alpine:latest",
|
|
Labels: map[string]string{container.LabelComponent: component, "group": "test1"},
|
|
},
|
|
HostConfig: &typescontainer.HostConfig{NetworkMode: "default"},
|
|
})
|
|
require.NoError(t, err)
|
|
testhelpers.ChanDiscard(stateC)
|
|
|
|
stateC, err3C := client.RunContainer(ctx, container.RunContainerParams{
|
|
ChanSize: 1,
|
|
ContainerConfig: &typescontainer.Config{
|
|
Image: "ghcr.io/rfwatson/mediamtx-alpine:latest",
|
|
Labels: map[string]string{container.LabelComponent: component, "group": "test2"},
|
|
},
|
|
HostConfig: &typescontainer.HostConfig{NetworkMode: "default"},
|
|
})
|
|
require.NoError(t, err)
|
|
testhelpers.ChanDiscard(stateC)
|
|
|
|
// check all containers in group 1 are running
|
|
require.Eventually(
|
|
t,
|
|
func() bool {
|
|
running, _ := client.ContainerRunning(ctx, client.ContainersWithLabels(map[string]string{"group": "test1"}))
|
|
return running
|
|
},
|
|
5*time.Second,
|
|
500*time.Millisecond,
|
|
"container group 1 not in RUNNING state",
|
|
)
|
|
// check all containers in group 2 are running
|
|
require.Eventually(
|
|
t,
|
|
func() bool {
|
|
running, _ := client.ContainerRunning(ctx, client.ContainersWithLabels(map[string]string{"group": "test2"}))
|
|
return running
|
|
},
|
|
2*time.Second,
|
|
500*time.Millisecond,
|
|
"container group 2 not in RUNNING state",
|
|
)
|
|
|
|
// remove group 1
|
|
err = client.RemoveContainers(ctx, client.ContainersWithLabels(map[string]string{"group": "test1"}))
|
|
require.NoError(t, err)
|
|
|
|
// check group 1 is not running
|
|
require.Eventually(
|
|
t,
|
|
func() bool {
|
|
var running bool
|
|
running, err = client.ContainerRunning(ctx, client.ContainersWithLabels(map[string]string{"group": "test1"}))
|
|
return err == nil && !running
|
|
},
|
|
2*time.Second,
|
|
500*time.Millisecond,
|
|
"container group 1 still in RUNNING state",
|
|
)
|
|
|
|
// check group 2 is still running
|
|
running, err := client.ContainerRunning(ctx, client.ContainersWithLabels(map[string]string{"group": "test2"}))
|
|
require.NoError(t, err)
|
|
assert.True(t, running)
|
|
|
|
assert.NoError(t, <-err1C)
|
|
assert.NoError(t, <-err2C)
|
|
|
|
client.Close()
|
|
|
|
assert.NoError(t, <-err3C)
|
|
}
|
|
|
|
func TestIntegrationContainerRestart(t *testing.T) {
|
|
const wantRestartCount = 3
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
t.Cleanup(cancel)
|
|
|
|
logger := testhelpers.NewTestLogger(t)
|
|
apiClient, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
|
|
require.NoError(t, err)
|
|
containerName := "octoplex-test-" + shortid.New().String()
|
|
component := "test-restart"
|
|
|
|
client, err := container.NewClient(ctx, apiClient, logger)
|
|
require.NoError(t, err)
|
|
defer client.Close()
|
|
|
|
containerStateC, errC := client.RunContainer(ctx, container.RunContainerParams{
|
|
Name: containerName,
|
|
ChanSize: 1,
|
|
ContainerConfig: &typescontainer.Config{
|
|
Image: "alpine:3.18",
|
|
Entrypoint: []string{"sleep", "1"},
|
|
Cmd: []string{"1"}, // 1 second
|
|
Labels: map[string]string{container.LabelComponent: component},
|
|
},
|
|
HostConfig: &typescontainer.HostConfig{
|
|
NetworkMode: "default",
|
|
},
|
|
ShouldRestart: func(_ int64, restartCount int, _ [][]byte, _ time.Duration) (bool, error) {
|
|
return restartCount < wantRestartCount, nil
|
|
},
|
|
RestartInterval: 1 * time.Second,
|
|
})
|
|
testhelpers.ChanRequireNoError(t, errC)
|
|
|
|
outer:
|
|
for {
|
|
select {
|
|
case containerState := <-containerStateC:
|
|
if containerState.Status == "running" {
|
|
break outer
|
|
}
|
|
case <-time.After(5 * time.Second):
|
|
require.Fail(t, "timeout waiting for container")
|
|
}
|
|
}
|
|
|
|
err = nil // reset error
|
|
done := make(chan struct{})
|
|
go func() {
|
|
defer close(done)
|
|
|
|
for {
|
|
containerState := <-containerStateC
|
|
if containerState.Status == domain.ContainerStatusExited {
|
|
if containerState.RestartCount != wantRestartCount {
|
|
err = fmt.Errorf("expected %d restarts, got %d", wantRestartCount, containerState.RestartCount)
|
|
}
|
|
break
|
|
}
|
|
}
|
|
}()
|
|
|
|
<-done
|
|
require.NoError(t, err)
|
|
}
|