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  }