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