f0c3dca49c
Copy the updated version of freeport (sdk/freeport), and tweak it for use in Nomad tests. This means staying below port 10000 to avoid conflicts with the lib/freeport that is still transitively used by the old version of consul that we vendor. Also provide implementations to find ephemeral ports of macOS and Windows environments. Ports acquired through freeport are supposed to be returned to freeport, which this change now also introduces. Many tests are modified to include calls to a cleanup function for Server objects. This should help quite a bit with some flakey tests, but not all of them. Our port problems will not go away completely until we upgrade our vendor version of consul. With Go modules, we'll probably do a 'replace' to swap out other copies of freeport with the one now in 'nomad/helper/freeport'.
206 lines
6.6 KiB
Go
206 lines
6.6 KiB
Go
package docker
|
|
|
|
import (
|
|
"encoding/json"
|
|
"os"
|
|
"testing"
|
|
"time"
|
|
|
|
docker "github.com/fsouza/go-dockerclient"
|
|
"github.com/hashicorp/nomad/client/testutil"
|
|
"github.com/hashicorp/nomad/helper/freeport"
|
|
"github.com/hashicorp/nomad/helper/uuid"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func fakeContainerList(t *testing.T) (nomadContainer, nonNomadContainer docker.APIContainers) {
|
|
path := "./test-resources/docker/reconciler_containers_list.json"
|
|
|
|
f, err := os.Open(path)
|
|
if err != nil {
|
|
t.Fatalf("failed to open file: %v", err)
|
|
}
|
|
|
|
var sampleContainerList []docker.APIContainers
|
|
err = json.NewDecoder(f).Decode(&sampleContainerList)
|
|
if err != nil {
|
|
t.Fatalf("failed to decode container list: %v", err)
|
|
}
|
|
|
|
return sampleContainerList[0], sampleContainerList[1]
|
|
}
|
|
|
|
func Test_HasMount(t *testing.T) {
|
|
nomadContainer, nonNomadContainer := fakeContainerList(t)
|
|
|
|
require.True(t, hasMount(nomadContainer, "/alloc"))
|
|
require.True(t, hasMount(nomadContainer, "/data"))
|
|
require.True(t, hasMount(nomadContainer, "/secrets"))
|
|
require.False(t, hasMount(nomadContainer, "/random"))
|
|
|
|
require.False(t, hasMount(nonNomadContainer, "/alloc"))
|
|
require.False(t, hasMount(nonNomadContainer, "/data"))
|
|
require.False(t, hasMount(nonNomadContainer, "/secrets"))
|
|
require.False(t, hasMount(nonNomadContainer, "/random"))
|
|
}
|
|
|
|
func Test_HasNomadName(t *testing.T) {
|
|
nomadContainer, nonNomadContainer := fakeContainerList(t)
|
|
|
|
require.True(t, hasNomadName(nomadContainer))
|
|
require.False(t, hasNomadName(nonNomadContainer))
|
|
}
|
|
|
|
// TestDanglingContainerRemoval asserts containers without corresponding tasks
|
|
// are removed after the creation grace period.
|
|
func TestDanglingContainerRemoval(t *testing.T) {
|
|
testutil.DockerCompatible(t)
|
|
|
|
// start two containers: one tracked nomad container, and one unrelated container
|
|
task, cfg, ports := dockerTask(t)
|
|
defer freeport.Return(ports)
|
|
require.NoError(t, task.EncodeConcreteDriverConfig(cfg))
|
|
|
|
client, d, handle, cleanup := dockerSetup(t, task)
|
|
defer cleanup()
|
|
require.NoError(t, d.WaitUntilStarted(task.ID, 5*time.Second))
|
|
|
|
nonNomadContainer, err := client.CreateContainer(docker.CreateContainerOptions{
|
|
Name: "mytest-image-" + uuid.Generate(),
|
|
Config: &docker.Config{
|
|
Image: cfg.Image,
|
|
Cmd: append([]string{cfg.Command}, cfg.Args...),
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
defer client.RemoveContainer(docker.RemoveContainerOptions{
|
|
ID: nonNomadContainer.ID,
|
|
Force: true,
|
|
})
|
|
|
|
err = client.StartContainer(nonNomadContainer.ID, nil)
|
|
require.NoError(t, err)
|
|
|
|
untrackedNomadContainer, err := client.CreateContainer(docker.CreateContainerOptions{
|
|
Name: "mytest-image-" + uuid.Generate(),
|
|
Config: &docker.Config{
|
|
Image: cfg.Image,
|
|
Cmd: append([]string{cfg.Command}, cfg.Args...),
|
|
Labels: map[string]string{
|
|
dockerLabelAllocID: uuid.Generate(),
|
|
},
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
defer client.RemoveContainer(docker.RemoveContainerOptions{
|
|
ID: untrackedNomadContainer.ID,
|
|
Force: true,
|
|
})
|
|
|
|
err = client.StartContainer(untrackedNomadContainer.ID, nil)
|
|
require.NoError(t, err)
|
|
|
|
dd := d.Impl().(*Driver)
|
|
|
|
reconciler := newReconciler(dd)
|
|
trackedContainers := map[string]bool{handle.containerID: true}
|
|
|
|
tf := reconciler.trackedContainers()
|
|
require.Contains(t, tf, handle.containerID)
|
|
require.NotContains(t, tf, untrackedNomadContainer)
|
|
require.NotContains(t, tf, nonNomadContainer.ID)
|
|
|
|
// assert tracked containers should never be untracked
|
|
untracked, err := reconciler.untrackedContainers(trackedContainers, time.Now())
|
|
require.NoError(t, err)
|
|
require.NotContains(t, untracked, handle.containerID)
|
|
require.NotContains(t, untracked, nonNomadContainer.ID)
|
|
require.Contains(t, untracked, untrackedNomadContainer.ID)
|
|
|
|
// assert we recognize nomad containers with appropriate cutoff
|
|
untracked, err = reconciler.untrackedContainers(map[string]bool{}, time.Now())
|
|
require.NoError(t, err)
|
|
require.Contains(t, untracked, handle.containerID)
|
|
require.Contains(t, untracked, untrackedNomadContainer.ID)
|
|
require.NotContains(t, untracked, nonNomadContainer.ID)
|
|
|
|
// but ignore if creation happened before cutoff
|
|
untracked, err = reconciler.untrackedContainers(map[string]bool{}, time.Now().Add(-1*time.Minute))
|
|
require.NoError(t, err)
|
|
require.NotContains(t, untracked, handle.containerID)
|
|
require.NotContains(t, untracked, untrackedNomadContainer.ID)
|
|
require.NotContains(t, untracked, nonNomadContainer.ID)
|
|
|
|
// a full integration tests to assert that containers are removed
|
|
prestineDriver := dockerDriverHarness(t, nil).Impl().(*Driver)
|
|
prestineDriver.config.GC.DanglingContainers = ContainerGCConfig{
|
|
Enabled: true,
|
|
period: 1 * time.Second,
|
|
CreationGrace: 0 * time.Second,
|
|
}
|
|
nReconciler := newReconciler(prestineDriver)
|
|
|
|
require.NoError(t, nReconciler.removeDanglingContainersIteration())
|
|
|
|
_, err = client.InspectContainer(nonNomadContainer.ID)
|
|
require.NoError(t, err)
|
|
|
|
_, err = client.InspectContainer(handle.containerID)
|
|
require.Error(t, err)
|
|
require.Contains(t, err.Error(), NoSuchContainerError)
|
|
|
|
_, err = client.InspectContainer(untrackedNomadContainer.ID)
|
|
require.Error(t, err)
|
|
require.Contains(t, err.Error(), NoSuchContainerError)
|
|
}
|
|
|
|
// TestDanglingContainerRemoval_Stopped asserts stopped containers without
|
|
// corresponding tasks are not removed even if after creation grace period.
|
|
func TestDanglingContainerRemoval_Stopped(t *testing.T) {
|
|
testutil.DockerCompatible(t)
|
|
|
|
_, cfg, ports := dockerTask(t)
|
|
defer freeport.Return(ports)
|
|
|
|
client := newTestDockerClient(t)
|
|
container, err := client.CreateContainer(docker.CreateContainerOptions{
|
|
Name: "mytest-image-" + uuid.Generate(),
|
|
Config: &docker.Config{
|
|
Image: cfg.Image,
|
|
Cmd: append([]string{cfg.Command}, cfg.Args...),
|
|
Labels: map[string]string{
|
|
dockerLabelAllocID: uuid.Generate(),
|
|
},
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
defer client.RemoveContainer(docker.RemoveContainerOptions{
|
|
ID: container.ID,
|
|
Force: true,
|
|
})
|
|
|
|
err = client.StartContainer(container.ID, nil)
|
|
require.NoError(t, err)
|
|
|
|
err = client.StopContainer(container.ID, 60)
|
|
require.NoError(t, err)
|
|
|
|
dd := dockerDriverHarness(t, nil).Impl().(*Driver)
|
|
reconciler := newReconciler(dd)
|
|
|
|
// assert nomad container is tracked, and we ignore stopped one
|
|
tf := reconciler.trackedContainers()
|
|
require.NotContains(t, tf, container.ID)
|
|
|
|
untracked, err := reconciler.untrackedContainers(map[string]bool{}, time.Now())
|
|
require.NoError(t, err)
|
|
require.NotContains(t, untracked, container.ID)
|
|
|
|
// if we start container again, it'll be marked as untracked
|
|
require.NoError(t, client.StartContainer(container.ID, nil))
|
|
|
|
untracked, err = reconciler.untrackedContainers(map[string]bool{}, time.Now())
|
|
require.NoError(t, err)
|
|
require.Contains(t, untracked, container.ID)
|
|
}
|