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