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  }