octoplex/internal/container/integration_test.go
Rob Watson 3523a1a34e
Some checks failed
ci-build / lint (push) Has been cancelled
ci-build / build (push) Has been cancelled
ci-build / release (push) Has been cancelled
feat(mediaserver): update MediaMTX image
2025-04-06 09:25:50 +02:00

230 lines
6.8 KiB
Go

//go:build integration
package container_test
import (
"context"
"errors"
"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 TestContainerRestart(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-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:latest",
Cmd: []string{"sleep", "1"},
Labels: map[string]string{container.LabelComponent: component},
},
HostConfig: &typescontainer.HostConfig{
NetworkMode: "default",
RestartPolicy: typescontainer.RestartPolicy{Name: "always"},
},
})
testhelpers.ChanRequireNoError(t, errC)
containerState := <-containerStateC
assert.Equal(t, "pulling", containerState.Status)
containerState = <-containerStateC
assert.Equal(t, "created", containerState.Status)
containerState = <-containerStateC
assert.Equal(t, "running", containerState.Status)
err = nil // reset error
done := make(chan struct{})
go func() {
defer close(done)
var count int
for {
containerState = <-containerStateC
if containerState.Status == domain.ContainerStatusRestarting {
break
} else if containerState.Status == domain.ContainerStatusExited {
err = errors.New("container exited unexpectedly")
} else if count >= 5 {
err = errors.New("container did not enter restarting state")
} else {
// wait for a few state changes
count++
}
}
}()
<-done
require.NoError(t, err)
}