github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/drivers/docker/reconcile_dangling_test.go (about) 1 package docker 2 3 import ( 4 "encoding/json" 5 "os" 6 "testing" 7 "time" 8 9 docker "github.com/fsouza/go-dockerclient" 10 "github.com/hashicorp/nomad/ci" 11 "github.com/hashicorp/nomad/client/testutil" 12 "github.com/hashicorp/nomad/helper/freeport" 13 "github.com/hashicorp/nomad/helper/uuid" 14 "github.com/stretchr/testify/require" 15 ) 16 17 func fakeContainerList(t *testing.T) (nomadContainer, nonNomadContainer docker.APIContainers) { 18 path := "./test-resources/docker/reconciler_containers_list.json" 19 20 f, err := os.Open(path) 21 if err != nil { 22 t.Fatalf("failed to open file: %v", err) 23 } 24 25 var sampleContainerList []docker.APIContainers 26 err = json.NewDecoder(f).Decode(&sampleContainerList) 27 if err != nil { 28 t.Fatalf("failed to decode container list: %v", err) 29 } 30 31 return sampleContainerList[0], sampleContainerList[1] 32 } 33 34 func Test_HasMount(t *testing.T) { 35 ci.Parallel(t) 36 37 nomadContainer, nonNomadContainer := fakeContainerList(t) 38 39 require.True(t, hasMount(nomadContainer, "/alloc")) 40 require.True(t, hasMount(nomadContainer, "/data")) 41 require.True(t, hasMount(nomadContainer, "/secrets")) 42 require.False(t, hasMount(nomadContainer, "/random")) 43 44 require.False(t, hasMount(nonNomadContainer, "/alloc")) 45 require.False(t, hasMount(nonNomadContainer, "/data")) 46 require.False(t, hasMount(nonNomadContainer, "/secrets")) 47 require.False(t, hasMount(nonNomadContainer, "/random")) 48 } 49 50 func Test_HasNomadName(t *testing.T) { 51 ci.Parallel(t) 52 53 nomadContainer, nonNomadContainer := fakeContainerList(t) 54 55 require.True(t, hasNomadName(nomadContainer)) 56 require.False(t, hasNomadName(nonNomadContainer)) 57 } 58 59 // TestDanglingContainerRemoval asserts containers without corresponding tasks 60 // are removed after the creation grace period. 61 func TestDanglingContainerRemoval(t *testing.T) { 62 ci.Parallel(t) 63 testutil.DockerCompatible(t) 64 65 // start two containers: one tracked nomad container, and one unrelated container 66 task, cfg, ports := dockerTask(t) 67 defer freeport.Return(ports) 68 require.NoError(t, task.EncodeConcreteDriverConfig(cfg)) 69 70 client, d, handle, cleanup := dockerSetup(t, task, nil) 71 defer cleanup() 72 require.NoError(t, d.WaitUntilStarted(task.ID, 5*time.Second)) 73 74 nonNomadContainer, err := client.CreateContainer(docker.CreateContainerOptions{ 75 Name: "mytest-image-" + uuid.Generate(), 76 Config: &docker.Config{ 77 Image: cfg.Image, 78 Cmd: append([]string{cfg.Command}, cfg.Args...), 79 }, 80 }) 81 require.NoError(t, err) 82 defer client.RemoveContainer(docker.RemoveContainerOptions{ 83 ID: nonNomadContainer.ID, 84 Force: true, 85 }) 86 87 err = client.StartContainer(nonNomadContainer.ID, nil) 88 require.NoError(t, err) 89 90 untrackedNomadContainer, err := client.CreateContainer(docker.CreateContainerOptions{ 91 Name: "mytest-image-" + uuid.Generate(), 92 Config: &docker.Config{ 93 Image: cfg.Image, 94 Cmd: append([]string{cfg.Command}, cfg.Args...), 95 Labels: map[string]string{ 96 dockerLabelAllocID: uuid.Generate(), 97 }, 98 }, 99 }) 100 require.NoError(t, err) 101 defer client.RemoveContainer(docker.RemoveContainerOptions{ 102 ID: untrackedNomadContainer.ID, 103 Force: true, 104 }) 105 106 err = client.StartContainer(untrackedNomadContainer.ID, nil) 107 require.NoError(t, err) 108 109 dd := d.Impl().(*Driver) 110 111 reconciler := newReconciler(dd) 112 trackedContainers := map[string]bool{handle.containerID: true} 113 114 tf := reconciler.trackedContainers() 115 require.Contains(t, tf, handle.containerID) 116 require.NotContains(t, tf, untrackedNomadContainer) 117 require.NotContains(t, tf, nonNomadContainer.ID) 118 119 // assert tracked containers should never be untracked 120 untracked, err := reconciler.untrackedContainers(trackedContainers, time.Now()) 121 require.NoError(t, err) 122 require.NotContains(t, untracked, handle.containerID) 123 require.NotContains(t, untracked, nonNomadContainer.ID) 124 require.Contains(t, untracked, untrackedNomadContainer.ID) 125 126 // assert we recognize nomad containers with appropriate cutoff 127 untracked, err = reconciler.untrackedContainers(map[string]bool{}, time.Now()) 128 require.NoError(t, err) 129 require.Contains(t, untracked, handle.containerID) 130 require.Contains(t, untracked, untrackedNomadContainer.ID) 131 require.NotContains(t, untracked, nonNomadContainer.ID) 132 133 // but ignore if creation happened before cutoff 134 untracked, err = reconciler.untrackedContainers(map[string]bool{}, time.Now().Add(-1*time.Minute)) 135 require.NoError(t, err) 136 require.NotContains(t, untracked, handle.containerID) 137 require.NotContains(t, untracked, untrackedNomadContainer.ID) 138 require.NotContains(t, untracked, nonNomadContainer.ID) 139 140 // a full integration tests to assert that containers are removed 141 prestineDriver := dockerDriverHarness(t, nil).Impl().(*Driver) 142 prestineDriver.config.GC.DanglingContainers = ContainerGCConfig{ 143 Enabled: true, 144 period: 1 * time.Second, 145 CreationGrace: 0 * time.Second, 146 } 147 nReconciler := newReconciler(prestineDriver) 148 149 require.NoError(t, nReconciler.removeDanglingContainersIteration()) 150 151 _, err = client.InspectContainer(nonNomadContainer.ID) 152 require.NoError(t, err) 153 154 _, err = client.InspectContainer(handle.containerID) 155 require.Error(t, err) 156 require.Contains(t, err.Error(), NoSuchContainerError) 157 158 _, err = client.InspectContainer(untrackedNomadContainer.ID) 159 require.Error(t, err) 160 require.Contains(t, err.Error(), NoSuchContainerError) 161 } 162 163 // TestDanglingContainerRemoval_Stopped asserts stopped containers without 164 // corresponding tasks are not removed even if after creation grace period. 165 func TestDanglingContainerRemoval_Stopped(t *testing.T) { 166 ci.Parallel(t) 167 testutil.DockerCompatible(t) 168 169 _, cfg, ports := dockerTask(t) 170 defer freeport.Return(ports) 171 172 client := newTestDockerClient(t) 173 container, err := client.CreateContainer(docker.CreateContainerOptions{ 174 Name: "mytest-image-" + uuid.Generate(), 175 Config: &docker.Config{ 176 Image: cfg.Image, 177 Cmd: append([]string{cfg.Command}, cfg.Args...), 178 Labels: map[string]string{ 179 dockerLabelAllocID: uuid.Generate(), 180 }, 181 }, 182 }) 183 require.NoError(t, err) 184 defer client.RemoveContainer(docker.RemoveContainerOptions{ 185 ID: container.ID, 186 Force: true, 187 }) 188 189 err = client.StartContainer(container.ID, nil) 190 require.NoError(t, err) 191 192 err = client.StopContainer(container.ID, 60) 193 require.NoError(t, err) 194 195 dd := dockerDriverHarness(t, nil).Impl().(*Driver) 196 reconciler := newReconciler(dd) 197 198 // assert nomad container is tracked, and we ignore stopped one 199 tf := reconciler.trackedContainers() 200 require.NotContains(t, tf, container.ID) 201 202 untracked, err := reconciler.untrackedContainers(map[string]bool{}, time.Now()) 203 require.NoError(t, err) 204 require.NotContains(t, untracked, container.ID) 205 206 // if we start container again, it'll be marked as untracked 207 require.NoError(t, client.StartContainer(container.ID, nil)) 208 209 untracked, err = reconciler.untrackedContainers(map[string]bool{}, time.Now()) 210 require.NoError(t, err) 211 require.Contains(t, untracked, container.ID) 212 }