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