github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/drivers/docker/docklog/docker_logger_test.go (about) 1 package docklog 2 3 import ( 4 "bytes" 5 "context" 6 "errors" 7 "fmt" 8 "runtime" 9 "testing" 10 "time" 11 12 docker "github.com/fsouza/go-dockerclient" 13 "github.com/stretchr/testify/require" 14 15 "github.com/hashicorp/nomad/ci" 16 ctu "github.com/hashicorp/nomad/client/testutil" 17 "github.com/hashicorp/nomad/helper/testlog" 18 "github.com/hashicorp/nomad/testutil" 19 ) 20 21 func testContainerDetails() (image string, imageName string, imageTag string) { 22 name := "busybox" 23 tag := "1" 24 25 if runtime.GOOS == "windows" { 26 name = "hashicorpdev/busybox-windows" 27 tag = "server2016-0.1" 28 } 29 30 if testutil.IsCI() { 31 // In CI, use HashiCorp Mirror to avoid DockerHub rate limiting 32 name = "docker.mirror.hashicorp.services/" + name 33 } 34 35 return name + ":" + tag, name, tag 36 } 37 38 func TestDockerLogger_Success(t *testing.T) { 39 ci.Parallel(t) 40 ctu.DockerCompatible(t) 41 42 require := require.New(t) 43 44 containerImage, containerImageName, containerImageTag := testContainerDetails() 45 46 client, err := docker.NewClientFromEnv() 47 if err != nil { 48 t.Skip("docker unavailable:", err) 49 } 50 51 if img, err := client.InspectImage(containerImage); err != nil || img == nil { 52 t.Log("image not found locally, downloading...") 53 err = client.PullImage(docker.PullImageOptions{ 54 Repository: containerImageName, 55 Tag: containerImageTag, 56 }, docker.AuthConfiguration{}) 57 require.NoError(err, "failed to pull image") 58 } 59 60 containerConf := docker.CreateContainerOptions{ 61 Config: &docker.Config{ 62 Cmd: []string{ 63 "sh", "-c", "touch ~/docklog; tail -f ~/docklog", 64 }, 65 Image: containerImage, 66 }, 67 Context: context.Background(), 68 } 69 70 container, err := client.CreateContainer(containerConf) 71 require.NoError(err) 72 73 defer client.RemoveContainer(docker.RemoveContainerOptions{ 74 ID: container.ID, 75 Force: true, 76 }) 77 78 err = client.StartContainer(container.ID, nil) 79 require.NoError(err) 80 81 testutil.WaitForResult(func() (bool, error) { 82 container, err = client.InspectContainer(container.ID) 83 if err != nil { 84 return false, err 85 } 86 if !container.State.Running { 87 return false, fmt.Errorf("container not running") 88 } 89 return true, nil 90 }, func(err error) { 91 require.NoError(err) 92 }) 93 94 stdout := &noopCloser{bytes.NewBuffer(nil)} 95 stderr := &noopCloser{bytes.NewBuffer(nil)} 96 97 dl := NewDockerLogger(testlog.HCLogger(t)).(*dockerLogger) 98 dl.stdout = stdout 99 dl.stderr = stderr 100 require.NoError(dl.Start(&StartOpts{ 101 ContainerID: container.ID, 102 })) 103 104 echoToContainer(t, client, container.ID, "abc") 105 echoToContainer(t, client, container.ID, "123") 106 107 testutil.WaitForResult(func() (bool, error) { 108 act := stdout.String() 109 if "abc\n123\n" != act { 110 return false, fmt.Errorf("expected abc\\n123\\n for stdout but got %s", act) 111 } 112 113 return true, nil 114 }, func(err error) { 115 require.NoError(err) 116 }) 117 } 118 119 func TestDockerLogger_Success_TTY(t *testing.T) { 120 ci.Parallel(t) 121 ctu.DockerCompatible(t) 122 123 require := require.New(t) 124 125 containerImage, containerImageName, containerImageTag := testContainerDetails() 126 127 client, err := docker.NewClientFromEnv() 128 if err != nil { 129 t.Skip("docker unavailable:", err) 130 } 131 132 if img, err := client.InspectImage(containerImage); err != nil || img == nil { 133 t.Log("image not found locally, downloading...") 134 err = client.PullImage(docker.PullImageOptions{ 135 Repository: containerImageName, 136 Tag: containerImageTag, 137 }, docker.AuthConfiguration{}) 138 require.NoError(err, "failed to pull image") 139 } 140 141 containerConf := docker.CreateContainerOptions{ 142 Config: &docker.Config{ 143 Cmd: []string{ 144 "sh", "-c", "touch ~/docklog; tail -f ~/docklog", 145 }, 146 Image: containerImage, 147 Tty: true, 148 }, 149 Context: context.Background(), 150 } 151 152 container, err := client.CreateContainer(containerConf) 153 require.NoError(err) 154 155 defer client.RemoveContainer(docker.RemoveContainerOptions{ 156 ID: container.ID, 157 Force: true, 158 }) 159 160 err = client.StartContainer(container.ID, nil) 161 require.NoError(err) 162 163 testutil.WaitForResult(func() (bool, error) { 164 container, err = client.InspectContainer(container.ID) 165 if err != nil { 166 return false, err 167 } 168 if !container.State.Running { 169 return false, fmt.Errorf("container not running") 170 } 171 return true, nil 172 }, func(err error) { 173 require.NoError(err) 174 }) 175 176 stdout := &noopCloser{bytes.NewBuffer(nil)} 177 stderr := &noopCloser{bytes.NewBuffer(nil)} 178 179 dl := NewDockerLogger(testlog.HCLogger(t)).(*dockerLogger) 180 dl.stdout = stdout 181 dl.stderr = stderr 182 require.NoError(dl.Start(&StartOpts{ 183 ContainerID: container.ID, 184 TTY: true, 185 })) 186 187 echoToContainer(t, client, container.ID, "abc") 188 echoToContainer(t, client, container.ID, "123") 189 190 testutil.WaitForResult(func() (bool, error) { 191 act := stdout.String() 192 if "abc\r\n123\r\n" != act { 193 return false, fmt.Errorf("expected abc\\n123\\n for stdout but got %s", act) 194 } 195 196 return true, nil 197 }, func(err error) { 198 require.NoError(err) 199 }) 200 } 201 202 func echoToContainer(t *testing.T, client *docker.Client, id string, line string) { 203 op := docker.CreateExecOptions{ 204 Container: id, 205 Cmd: []string{ 206 "/bin/sh", "-c", 207 fmt.Sprintf("echo %s >>~/docklog", line), 208 }, 209 } 210 211 exec, err := client.CreateExec(op) 212 require.NoError(t, err) 213 require.NoError(t, client.StartExec(exec.ID, docker.StartExecOptions{Detach: true})) 214 } 215 216 func TestDockerLogger_LoggingNotSupported(t *testing.T) { 217 ci.Parallel(t) 218 ctu.DockerCompatible(t) 219 220 containerImage, containerImageName, containerImageTag := testContainerDetails() 221 222 client, err := docker.NewClientFromEnv() 223 if err != nil { 224 t.Skip("docker unavailable:", err) 225 } 226 227 if img, err := client.InspectImage(containerImage); err != nil || img == nil { 228 t.Log("image not found locally, downloading...") 229 err = client.PullImage(docker.PullImageOptions{ 230 Repository: containerImageName, 231 Tag: containerImageTag, 232 }, docker.AuthConfiguration{}) 233 require.NoError(t, err, "failed to pull image") 234 } 235 236 containerConf := docker.CreateContainerOptions{ 237 Config: &docker.Config{ 238 Cmd: []string{ 239 "sh", "-c", "touch ~/docklog; tail -f ~/docklog", 240 }, 241 Image: containerImage, 242 }, 243 HostConfig: &docker.HostConfig{ 244 LogConfig: docker.LogConfig{ 245 Type: "none", 246 Config: map[string]string{}, 247 }, 248 }, 249 Context: context.Background(), 250 } 251 252 container, err := client.CreateContainer(containerConf) 253 require.NoError(t, err) 254 255 defer client.RemoveContainer(docker.RemoveContainerOptions{ 256 ID: container.ID, 257 Force: true, 258 }) 259 260 err = client.StartContainer(container.ID, nil) 261 require.NoError(t, err) 262 263 testutil.WaitForResult(func() (bool, error) { 264 container, err = client.InspectContainer(container.ID) 265 if err != nil { 266 return false, err 267 } 268 if !container.State.Running { 269 return false, fmt.Errorf("container not running") 270 } 271 return true, nil 272 }, func(err error) { 273 require.NoError(t, err) 274 }) 275 276 stdout := &noopCloser{bytes.NewBuffer(nil)} 277 stderr := &noopCloser{bytes.NewBuffer(nil)} 278 279 dl := NewDockerLogger(testlog.HCLogger(t)).(*dockerLogger) 280 dl.stdout = stdout 281 dl.stderr = stderr 282 require.NoError(t, dl.Start(&StartOpts{ 283 ContainerID: container.ID, 284 })) 285 286 select { 287 case <-dl.doneCh: 288 case <-time.After(10 * time.Second): 289 require.Fail(t, "timeout while waiting for docker_logging to terminate") 290 } 291 } 292 293 type noopCloser struct { 294 *bytes.Buffer 295 } 296 297 func (*noopCloser) Close() error { 298 return nil 299 } 300 301 func TestNextBackoff(t *testing.T) { 302 ci.Parallel(t) 303 304 cases := []struct { 305 currentBackoff float64 306 min float64 307 max float64 308 }{ 309 {0.0, 0.5, 1.15}, 310 {5.0, 5.0, 16}, 311 {120, 120, 120}, 312 } 313 314 for _, c := range cases { 315 t.Run(fmt.Sprintf("case %v", c.currentBackoff), func(t *testing.T) { 316 next := nextBackoff(c.currentBackoff) 317 t.Logf("computed backoff(%v) = %v", c.currentBackoff, next) 318 319 require.True(t, next >= c.min, "next backoff is smaller than expected") 320 require.True(t, next <= c.max, "next backoff is larger than expected") 321 }) 322 } 323 } 324 325 func TestIsLoggingTerminalError(t *testing.T) { 326 ci.Parallel(t) 327 328 terminalErrs := []error{ 329 errors.New("docker returned: configured logging driver does not support reading"), 330 &docker.Error{ 331 Status: 501, 332 Message: "configured logging driver does not support reading", 333 }, 334 &docker.Error{ 335 Status: 501, 336 Message: "not implemented", 337 }, 338 } 339 340 for _, err := range terminalErrs { 341 require.Truef(t, isLoggingTerminalError(err), "error should be terminal: %v", err) 342 } 343 344 nonTerminalErrs := []error{ 345 errors.New("not expected"), 346 &docker.Error{ 347 Status: 503, 348 Message: "Service Unavailable", 349 }, 350 } 351 352 for _, err := range nonTerminalErrs { 353 require.Falsef(t, isLoggingTerminalError(err), "error should be terminal: %v", err) 354 } 355 }