Skip to content

Commit

Permalink
TestDaemonProxy: use new scanners to check logs
Browse files Browse the repository at this point in the history
Also fixes up some cleanup issues.

Signed-off-by: Brian Goff <cpuguy83@gmail.com>
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
  • Loading branch information
cpuguy83 authored and thaJeztah committed Jul 31, 2023
1 parent 476e788 commit 1a51898
Show file tree
Hide file tree
Showing 2 changed files with 117 additions and 71 deletions.
142 changes: 80 additions & 62 deletions integration/daemon/daemon_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import (
"os/exec"
"path/filepath"
"runtime"
"strings"
"syscall"
"testing"

Expand Down Expand Up @@ -170,27 +169,34 @@ func TestDaemonProxy(t *testing.T) {
skip.If(t, runtime.GOOS == "windows", "cannot start multiple daemons on windows")
skip.If(t, os.Getenv("DOCKER_ROOTLESS") != "", "cannot connect to localhost proxy in rootless environment")

var received string
proxyServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
received = r.Host
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte("OK"))
}))
defer proxyServer.Close()
newProxy := func(rcvd *string, t *testing.T) *httptest.Server {
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
*rcvd = r.Host
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte("OK"))
}))
t.Cleanup(s.Close)
return s
}

const userPass = "myuser:mypassword@"

// Configure proxy through env-vars
t.Run("environment variables", func(t *testing.T) {
t.Setenv("HTTP_PROXY", proxyServer.URL)
t.Setenv("HTTPS_PROXY", proxyServer.URL)
t.Setenv("NO_PROXY", "example.com")
t.Parallel()

d := daemon.New(t)
c := d.NewClientT(t)
defer func() { _ = c.Close() }()
ctx := context.Background()
d.Start(t)
var received string
proxyServer := newProxy(&received, t)

d := daemon.New(t, daemon.WithEnvVars(
"HTTP_PROXY="+proxyServer.URL,
"HTTPS_PROXY="+proxyServer.URL,
"NO_PROXY=example.com",
))
c := d.NewClientT(t)

d.Start(t, "--iptables=false")
defer d.Stop(t)

info := d.Info(t)
Expand All @@ -210,35 +216,45 @@ func TestDaemonProxy(t *testing.T) {

// Configure proxy through command-line flags
t.Run("command-line options", func(t *testing.T) {
t.Setenv("HTTP_PROXY", "http://"+userPass+"from-env-http.invalid")
t.Setenv("http_proxy", "http://"+userPass+"from-env-http.invalid")
t.Setenv("HTTPS_PROXY", "https://"+userPass+"myuser:mypassword@from-env-https.invalid")
t.Setenv("https_proxy", "https://"+userPass+"myuser:mypassword@from-env-https.invalid")
t.Setenv("NO_PROXY", "ignore.invalid")
t.Setenv("no_proxy", "ignore.invalid")
t.Parallel()

d := daemon.New(t)
d.Start(t, "--http-proxy", proxyServer.URL, "--https-proxy", proxyServer.URL, "--no-proxy", "example.com")
ctx := context.Background()
var received string
proxyServer := newProxy(&received, t)

d := daemon.New(t, daemon.WithEnvVars(
"HTTP_PROXY="+"http://"+userPass+"from-env-http.invalid",
"http_proxy="+"http://"+userPass+"from-env-http.invalid",
"HTTPS_PROXY="+"https://"+userPass+"myuser:mypassword@from-env-https-invalid",
"https_proxy="+"https://"+userPass+"myuser:mypassword@from-env-https-invalid",
"NO_PROXY=ignore.invalid",
"no_proxy=ignore.invalid",
))
d.Start(t, "--iptables=false", "--http-proxy", proxyServer.URL, "--https-proxy", proxyServer.URL, "--no-proxy", "example.com")
defer d.Stop(t)

logs, err := d.ReadLogFile()
assert.NilError(t, err)
assert.Assert(t, is.Contains(string(logs), "overriding existing proxy variable with value from configuration"))
for _, v := range []string{"http_proxy", "HTTP_PROXY", "https_proxy", "HTTPS_PROXY", "no_proxy", "NO_PROXY"} {
assert.Assert(t, is.Contains(string(logs), "name="+v))
assert.Assert(t, !strings.Contains(string(logs), userPass), "logs should not contain the non-sanitized proxy URL: %s", string(logs))
}

c := d.NewClientT(t)
defer func() { _ = c.Close() }()
ctx := context.Background()

info := d.Info(t)
assert.Check(t, is.Equal(info.HTTPProxy, proxyServer.URL))
assert.Check(t, is.Equal(info.HTTPSProxy, proxyServer.URL))
assert.Check(t, is.Equal(info.NoProxy, "example.com"))

_, err = c.ImagePull(ctx, "example.org:5001/some/image:latest", types.ImagePullOptions{})
ok, _ := d.ScanLogsT(ctx, t, daemon.ScanLogsMatchAll(
"overriding existing proxy variable with value from configuration",
"http_proxy",
"HTTP_PROXY",
"https_proxy",
"HTTPS_PROXY",
"no_proxy",
"NO_PROXY",
))
assert.Assert(t, ok)

ok, logs := d.ScanLogsT(ctx, t, daemon.ScanLogsMatchString(userPass))
assert.Assert(t, !ok, "logs should not contain the non-sanitized proxy URL: %s", logs)

_, err := c.ImagePull(ctx, "example.org:5001/some/image:latest", types.ImagePullOptions{})
assert.ErrorContains(t, err, "", "pulling should have failed")
assert.Equal(t, received, "example.org:5001")

Expand All @@ -250,52 +266,57 @@ func TestDaemonProxy(t *testing.T) {

// Configure proxy through configuration file
t.Run("configuration file", func(t *testing.T) {
t.Setenv("HTTP_PROXY", "http://"+userPass+"from-env-http.invalid")
t.Setenv("http_proxy", "http://"+userPass+"from-env-http.invalid")
t.Setenv("HTTPS_PROXY", "https://"+userPass+"myuser:mypassword@from-env-https.invalid")
t.Setenv("https_proxy", "https://"+userPass+"myuser:mypassword@from-env-https.invalid")
t.Setenv("NO_PROXY", "ignore.invalid")
t.Setenv("no_proxy", "ignore.invalid")
t.Parallel()
ctx := context.Background()

d := daemon.New(t)
var received string
proxyServer := newProxy(&received, t)

d := daemon.New(t, daemon.WithEnvVars(
"HTTP_PROXY="+"http://"+userPass+"from-env-http.invalid",
"http_proxy="+"http://"+userPass+"from-env-http.invalid",
"HTTPS_PROXY="+"https://"+userPass+"myuser:mypassword@from-env-https-invalid",
"https_proxy="+"https://"+userPass+"myuser:mypassword@from-env-https-invalid",
"NO_PROXY=ignore.invalid",
"no_proxy=ignore.invalid",
))
c := d.NewClientT(t)
defer func() { _ = c.Close() }()
ctx := context.Background()

configFile := filepath.Join(d.RootDir(), "daemon.json")
configJSON := fmt.Sprintf(`{"proxies":{"http-proxy":%[1]q, "https-proxy": %[1]q, "no-proxy": "example.com"}}`, proxyServer.URL)
assert.NilError(t, os.WriteFile(configFile, []byte(configJSON), 0644))

d.Start(t, "--config-file", configFile)
d.Start(t, "--iptables=false", "--config-file", configFile)
defer d.Stop(t)

info := d.Info(t)
assert.Check(t, is.Equal(info.HTTPProxy, proxyServer.URL))
assert.Check(t, is.Equal(info.HTTPSProxy, proxyServer.URL))
assert.Check(t, is.Equal(info.NoProxy, "example.com"))

logs, err := d.ReadLogFile()
assert.NilError(t, err)
assert.Assert(t, is.Contains(string(logs), "overriding existing proxy variable with value from configuration"))
for _, v := range []string{"http_proxy", "HTTP_PROXY", "https_proxy", "HTTPS_PROXY", "no_proxy", "NO_PROXY"} {
assert.Assert(t, is.Contains(string(logs), "name="+v))
assert.Assert(t, !strings.Contains(string(logs), userPass), "logs should not contain the non-sanitized proxy URL: %s", string(logs))
}

_, err = c.ImagePull(ctx, "example.org:5002/some/image:latest", types.ImagePullOptions{})
d.ScanLogsT(ctx, t, daemon.ScanLogsMatchAll(
"overriding existing proxy variable with value from configuration",
"http_proxy",
"HTTP_PROXY",
"https_proxy",
"HTTPS_PROXY",
"no_proxy",
"NO_PROXY",
))

_, err := c.ImagePull(ctx, "example.org:5002/some/image:latest", types.ImagePullOptions{})
assert.ErrorContains(t, err, "", "pulling should have failed")
assert.Equal(t, received, "example.org:5002")

// Test NoProxy: example.com should not hit the proxy, and "received" variable should not be changed.
_, err = c.ImagePull(ctx, "example.com/some/image:latest", types.ImagePullOptions{})
assert.ErrorContains(t, err, "", "pulling should have failed")
assert.Equal(t, received, "example.org:5002", "should not have used proxy")

d.Stop(t)
})

// Conflicting options (passed both through command-line options and config file)
t.Run("conflicting options", func(t *testing.T) {
ctx := context.Background()
const (
proxyRawURL = "https://" + userPass + "example.org"
proxyURL = "https://xxxxx:xxxxx@example.org"
Expand All @@ -309,13 +330,12 @@ func TestDaemonProxy(t *testing.T) {

err := d.StartWithError("--http-proxy", proxyRawURL, "--https-proxy", proxyRawURL, "--no-proxy", "example.com", "--config-file", configFile, "--validate")
assert.ErrorContains(t, err, "daemon exited during startup")
logs, err := d.ReadLogFile()
assert.NilError(t, err)

expected := fmt.Sprintf(
`the following directives are specified both as a flag and in the configuration file: http-proxy: (from flag: %[1]s, from file: %[1]s), https-proxy: (from flag: %[1]s, from file: %[1]s), no-proxy: (from flag: example.com, from file: example.com)`,
proxyURL,
)
assert.Assert(t, is.Contains(string(logs), expected))
poll.WaitOn(t, d.PollCheckLogs(ctx, daemon.ScanLogsMatchString(expected)))
})

// Make sure values are sanitized when reloading the daemon-config
Expand All @@ -334,11 +354,9 @@ func TestDaemonProxy(t *testing.T) {
err := d.Signal(syscall.SIGHUP)
assert.NilError(t, err)

poll.WaitOn(t, d.PollCheckLogs(ctx, "Reloaded configuration:"))
poll.WaitOn(t, d.PollCheckLogs(ctx, proxyURL))
poll.WaitOn(t, d.PollCheckLogs(ctx, daemon.ScanLogsMatchAll("Reloaded configuration:", proxyURL)))

ok, logs, err := d.ScanLogs(ctx, userPass)
assert.NilError(t, err)
ok, logs := d.ScanLogsT(ctx, t, daemon.ScanLogsMatchString(userPass))
assert.Assert(t, !ok, "logs should not contain the non-sanitized proxy URL: %s", logs)
})
}
Expand Down
46 changes: 37 additions & 9 deletions testutil/daemon/daemon.go
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,7 @@ func (d *Daemon) NewClientT(t testing.TB, extraOpts ...client.Opt) *client.Clien

c, err := d.NewClient(extraOpts...)
assert.NilError(t, err, "[%s] could not create daemon client", d.id)
t.Cleanup(func() { c.Close() })
return c
}

Expand Down Expand Up @@ -313,23 +314,51 @@ func (d *Daemon) TailLogsT(t LogT, n int) {
}
}

// PollCheckLogs is a poll.Check that checks the daemon logs for the passed in string (`contains`).
func (d *Daemon) PollCheckLogs(ctx context.Context, contains string) poll.Check {
// PollCheckLogs is a poll.Check that checks the daemon logs using the passed in match function.
func (d *Daemon) PollCheckLogs(ctx context.Context, match func(s string) bool) poll.Check {
return func(t poll.LogT) poll.Result {
ok, _, err := d.ScanLogs(ctx, contains)
ok, _, err := d.ScanLogs(ctx, match)
if err != nil {
return poll.Error(err)
}
if !ok {
return poll.Continue("waiting for %q in daemon logs", contains)
return poll.Continue("waiting for daemon logs match")
}
return poll.Success()
}
}

// ScanLogs scans the daemon logs for the passed in string (`contains`).
// If the context is canceled, the function returns false but does not error out the test.
func (d *Daemon) ScanLogs(ctx context.Context, contains string) (bool, string, error) {
// ScanLogsMatchString returns a function that can be used to scan the daemon logs for the passed in string (`contains`).
func ScanLogsMatchString(contains string) func(string) bool {
return func(line string) bool {
return strings.Contains(line, contains)
}
}

// ScanLogsMatchAll returns a function that can be used to scan the daemon logs until *all* the passed in strings are matched
func ScanLogsMatchAll(contains ...string) func(string) bool {
matched := make(map[string]bool)
return func(line string) bool {
for _, c := range contains {
if strings.Contains(line, c) {
matched[c] = true
}
}
return len(matched) == len(contains)
}
}

// ScanLogsT uses `ScanLogs` to match the daemon logs using the passed in match function.
// If there is an error or the match fails, the test will fail.
func (d *Daemon) ScanLogsT(ctx context.Context, t testing.TB, match func(s string) bool) (bool, string) {
t.Helper()
ok, line, err := d.ScanLogs(ctx, match)
assert.NilError(t, err)
return ok, line
}

// ScanLogs scans the daemon logs and passes each line to the match function.
func (d *Daemon) ScanLogs(ctx context.Context, match func(s string) bool) (bool, string, error) {
stat, err := d.logFile.Stat()
if err != nil {
return false, "", err
Expand All @@ -338,7 +367,7 @@ func (d *Daemon) ScanLogs(ctx context.Context, contains string) (bool, string, e

scanner := bufio.NewScanner(rdr)
for scanner.Scan() {
if strings.Contains(scanner.Text(), contains) {
if match(scanner.Text()) {
return true, scanner.Text(), nil
}
select {
Expand All @@ -364,7 +393,6 @@ func (d *Daemon) TailLogs(n int) ([][]byte, error) {
}

return lines, nil

}

// Start starts the daemon and return once it is ready to receive requests.
Expand Down

0 comments on commit 1a51898

Please sign in to comment.