mirror of
https://github.com/kopia/kopia.git
synced 2026-01-14 09:27:53 -05:00
174 lines
5.2 KiB
Go
174 lines
5.2 KiB
Go
//go:build linux || darwin
|
|
|
|
package socketactivation_test
|
|
|
|
import (
|
|
"net"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
"sync/atomic"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/kopia/kopia/internal/testlogging"
|
|
"github.com/kopia/kopia/internal/testutil"
|
|
"github.com/kopia/kopia/tests/testenv"
|
|
)
|
|
|
|
func TestServerControlSocketActivated(t *testing.T) {
|
|
var port int
|
|
|
|
serverExe := os.Getenv("KOPIA_SERVER_EXE")
|
|
if serverExe == "" {
|
|
t.Skip("skipping socket-activation test")
|
|
}
|
|
|
|
runner := testenv.NewExeRunnerWithBinary(t, serverExe)
|
|
env := testenv.NewCLITest(t, testenv.RepoFormatNotImportant, runner)
|
|
|
|
dir0 := testutil.TempDirectory(t)
|
|
|
|
env.RunAndExpectSuccess(t, "repo", "create", "filesystem", "--path", env.RepoDir, "--override-username=another-user", "--override-hostname=another-host")
|
|
env.RunAndExpectSuccess(t, "snap", "create", dir0)
|
|
|
|
// The KOPIA_EXE wrapper will set the LISTEN_PID variable for us
|
|
env.Environment["LISTEN_FDS"] = "1"
|
|
|
|
ctx := testlogging.Context(t)
|
|
l1, err := (&net.ListenConfig{}).Listen(ctx, "tcp", ":0")
|
|
|
|
require.NoError(t, err, "Failed to open Listener")
|
|
|
|
t.Cleanup(func() { l1.Close() })
|
|
|
|
port = testutil.EnsureType[*net.TCPAddr](t, l1.Addr()).Port
|
|
|
|
t.Logf("Activating socket on port %v", port)
|
|
|
|
l1File, err := testutil.EnsureType[*net.TCPListener](t, l1).File()
|
|
require.NoError(t, err, "failed to get filehandle for socket")
|
|
|
|
serverStarted := make(chan struct{})
|
|
serverStopped := make(chan struct{})
|
|
|
|
var sp testutil.ServerParameters
|
|
|
|
go func() {
|
|
runner.ExtraFiles = append(runner.ExtraFiles, l1File)
|
|
wait, _ := env.RunAndProcessStderr(t, sp.ProcessOutput,
|
|
"server", "start", "--insecure", "--random-server-control-password", "--address=127.0.0.1:0")
|
|
|
|
l1File.Close()
|
|
close(serverStarted)
|
|
|
|
wait()
|
|
|
|
close(serverStopped)
|
|
}()
|
|
|
|
select {
|
|
case <-serverStarted:
|
|
require.NotEmpty(t, sp.BaseURL, "Failed to start server")
|
|
t.Logf("server started on %v", sp.BaseURL)
|
|
|
|
case <-time.After(15 * time.Second):
|
|
t.Fatal("server did not start in time")
|
|
}
|
|
|
|
require.Contains(t, sp.BaseURL, ":"+strconv.Itoa(port))
|
|
|
|
checkServerStatusFn := func(collect *assert.CollectT) {
|
|
lines := env.RunAndExpectSuccess(t, "server", "status", "--address", "http://127.0.0.1:"+strconv.Itoa(port), "--server-control-password", sp.ServerControlPassword, "--remote")
|
|
require.Len(collect, lines, 1)
|
|
require.Contains(collect, lines, "IDLE: another-user@another-host:"+dir0)
|
|
}
|
|
|
|
require.EventuallyWithT(t, checkServerStatusFn, 30*time.Second, 2*time.Second, "could not get server status, perhaps it was not listening on the control endpoint yet?")
|
|
|
|
env.RunAndExpectSuccess(t, "server", "shutdown", "--address", sp.BaseURL, "--server-control-password", sp.ServerControlPassword)
|
|
|
|
select {
|
|
case <-serverStopped:
|
|
t.Log("server shut down")
|
|
|
|
case <-time.After(15 * time.Second):
|
|
t.Fatal("server did not shutdown in time")
|
|
}
|
|
}
|
|
|
|
func TestServerControlSocketActivatedTooManyFDs(t *testing.T) {
|
|
serverExe := os.Getenv("KOPIA_SERVER_EXE")
|
|
if serverExe == "" {
|
|
t.Skip("skipping socket-activation test")
|
|
}
|
|
|
|
runner := testenv.NewExeRunnerWithBinary(t, serverExe)
|
|
env := testenv.NewCLITest(t, testenv.RepoFormatNotImportant, runner)
|
|
|
|
env.RunAndExpectSuccess(t, "repo", "create", "filesystem", "--path", env.RepoDir, "--override-username=another-user", "--override-hostname=another-host")
|
|
|
|
// create 2 file descriptor for a single socket and pass the descriptors to the server
|
|
ctx := testlogging.Context(t)
|
|
l1, err := (&net.ListenConfig{}).Listen(ctx, "tcp", ":0")
|
|
|
|
require.NoError(t, err, "Failed to open Listener")
|
|
|
|
t.Cleanup(func() { l1.Close() })
|
|
|
|
port := testutil.EnsureType[*net.TCPAddr](t, l1.Addr()).Port
|
|
|
|
t.Logf("activation socket port %v", port)
|
|
|
|
listener := testutil.EnsureType[*net.TCPListener](t, l1)
|
|
|
|
l1File, err := listener.File()
|
|
require.NoError(t, err, "failed to get 1st filehandle for socket")
|
|
|
|
t.Cleanup(func() { l1File.Close() })
|
|
|
|
l2File, err := listener.File()
|
|
require.NoError(t, err, "failed to get 2nd filehandle for socket")
|
|
|
|
t.Cleanup(func() { l2File.Close() })
|
|
|
|
runner.ExtraFiles = append(runner.ExtraFiles, l1File, l2File)
|
|
// The KOPIA_EXE wrapper will set the LISTEN_PID variable for us
|
|
env.Environment["LISTEN_FDS"] = "2"
|
|
|
|
var gotExpectedErrorMessage atomic.Bool
|
|
|
|
stderrAsyncCallback := func(line string) {
|
|
if strings.Contains(line, "Too many activated sockets found. Expected 1, got 2") {
|
|
gotExpectedErrorMessage.Store(true)
|
|
}
|
|
}
|
|
|
|
// although the server is expected to stop quickly with an error, the server's
|
|
// stderr is processed async to avoid test deadlocks if the server continues
|
|
// to run and does not exit.
|
|
wait, kill := env.RunAndProcessStderrAsync(t, func(string) bool { return false }, stderrAsyncCallback, "server", "start", "--insecure", "--random-server-control-password", "--address=127.0.0.1:0")
|
|
|
|
t.Cleanup(kill)
|
|
|
|
serverStopped := make(chan error)
|
|
go func() {
|
|
defer close(serverStopped)
|
|
|
|
serverStopped <- wait()
|
|
}()
|
|
|
|
select {
|
|
case err := <-serverStopped:
|
|
require.Error(t, err, "server did not exit with an error")
|
|
t.Log("Done")
|
|
case <-time.After(30 * time.Second):
|
|
t.Fatal("server did not exit in time")
|
|
}
|
|
|
|
require.True(t, gotExpectedErrorMessage.Load(), "expected server's stderr to contain a line along the lines of 'Too many activated sockets ...'")
|
|
}
|