Enhancement: option to send HTTP over unix socket
add tests for unix socket connection switch HTTP rest-server test to use any free port allow rest-server test graceful shutdown opportunity
This commit is contained in:
parent
d1d773cfcd
commit
6e775d3787
7 changed files with 182 additions and 41 deletions
|
@ -1,11 +1,18 @@
|
|||
//go:build go1.20
|
||||
// +build go1.20
|
||||
|
||||
package rest_test
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"net"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"strings"
|
||||
"syscall"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
|
@ -14,54 +21,133 @@ import (
|
|||
rtest "github.com/restic/restic/internal/test"
|
||||
)
|
||||
|
||||
func runRESTServer(ctx context.Context, t testing.TB, dir string) (*url.URL, func()) {
|
||||
var (
|
||||
serverStartedRE = regexp.MustCompile("^start server on (.*)$")
|
||||
)
|
||||
|
||||
func runRESTServer(ctx context.Context, t testing.TB, dir, reqListenAddr string) (*url.URL, func()) {
|
||||
srv, err := exec.LookPath("rest-server")
|
||||
if err != nil {
|
||||
t.Skip(err)
|
||||
}
|
||||
|
||||
cmd := exec.CommandContext(ctx, srv, "--no-auth", "--path", dir)
|
||||
// create our own context, so that our cleanup can cancel and wait for completion
|
||||
// this will ensure any open ports, open unix sockets etc are properly closed
|
||||
processCtx, cancel := context.WithCancel(ctx)
|
||||
cmd := exec.CommandContext(processCtx, srv, "--no-auth", "--path", dir, "--listen", reqListenAddr)
|
||||
|
||||
// this cancel func is called by when the process context is done
|
||||
cmd.Cancel = func() error {
|
||||
// we execute in a Go-routine as we know the caller will
|
||||
// be waiting on a .Wait() regardless
|
||||
go func() {
|
||||
// try to send a graceful termination signal
|
||||
if cmd.Process.Signal(syscall.SIGTERM) == nil {
|
||||
// if we succeed, then wait a few seconds
|
||||
time.Sleep(2 * time.Second)
|
||||
}
|
||||
// and then make sure it's killed either way, ignoring any error code
|
||||
_ = cmd.Process.Kill()
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
// this is the cleanup function that we return the caller,
|
||||
// which will cancel our process context, and then wait for it to finish
|
||||
cleanup := func() {
|
||||
cancel()
|
||||
_ = cmd.Wait()
|
||||
}
|
||||
|
||||
// but in-case we don't finish this method, e.g. by calling t.Fatal()
|
||||
// we also defer a call to clean it up ourselves, guarded by a flag to
|
||||
// indicate that we returned the function to the caller to deal with.
|
||||
callerWillCleanUp := false
|
||||
defer func() {
|
||||
if !callerWillCleanUp {
|
||||
cleanup()
|
||||
}
|
||||
}()
|
||||
|
||||
// send stdout to our std out
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stdout
|
||||
if err := cmd.Start(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// wait until the TCP port is reachable
|
||||
var success bool
|
||||
for i := 0; i < 10; i++ {
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
|
||||
c, err := net.Dial("tcp", "localhost:8000")
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
success = true
|
||||
if err := c.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
if !success {
|
||||
t.Fatal("unable to connect to rest server")
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
url, err := url.Parse("http://localhost:8000/restic-test/")
|
||||
// capture stderr with a pipe, as we want to examine this output
|
||||
// to determine when the server is started and listening.
|
||||
cmdErr, err := cmd.StderrPipe()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
cleanup := func() {
|
||||
if err := cmd.Process.Kill(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// ignore errors, we've killed the process
|
||||
_ = cmd.Wait()
|
||||
// start the rest-server
|
||||
if err := cmd.Start(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// create a channel to receive the actual listen address on
|
||||
listenAddrCh := make(chan string)
|
||||
go func() {
|
||||
defer close(listenAddrCh)
|
||||
matched := false
|
||||
br := bufio.NewReader(cmdErr)
|
||||
for {
|
||||
line, err := br.ReadString('\n')
|
||||
if err != nil {
|
||||
// we ignore errors, as code that relies on this
|
||||
// will happily fail via timeout and empty closed
|
||||
// channel.
|
||||
return
|
||||
}
|
||||
|
||||
line = strings.Trim(line, "\r\n")
|
||||
if !matched {
|
||||
// look for the server started message, and return the address
|
||||
// that it's listening on
|
||||
matchedServerListen := serverStartedRE.FindSubmatch([]byte(line))
|
||||
if len(matchedServerListen) == 2 {
|
||||
listenAddrCh <- string(matchedServerListen[1])
|
||||
matched = true
|
||||
}
|
||||
}
|
||||
fmt.Fprintln(os.Stdout, line) // print all output to console
|
||||
}
|
||||
}()
|
||||
|
||||
// wait for us to get an address,
|
||||
// or the parent context to cancel,
|
||||
// or for us to timeout
|
||||
var actualListenAddr string
|
||||
select {
|
||||
case <-processCtx.Done():
|
||||
t.Fatal(context.Canceled)
|
||||
case <-time.NewTimer(2 * time.Second).C:
|
||||
t.Fatal(context.DeadlineExceeded)
|
||||
case a, ok := <-listenAddrCh:
|
||||
if !ok {
|
||||
t.Fatal(context.Canceled)
|
||||
}
|
||||
actualListenAddr = a
|
||||
}
|
||||
|
||||
// this translate the address that the server is listening on
|
||||
// to a URL suitable for us to connect to
|
||||
var addrToConnectTo string
|
||||
if strings.HasPrefix(reqListenAddr, "unix:") {
|
||||
addrToConnectTo = fmt.Sprintf("http+unix://%s:/restic-test/", actualListenAddr)
|
||||
} else {
|
||||
// while we may listen on 0.0.0.0, we connect to localhost
|
||||
addrToConnectTo = fmt.Sprintf("http://%s/restic-test/", strings.Replace(actualListenAddr, "0.0.0.0", "localhost", 1))
|
||||
}
|
||||
|
||||
// parse to a URL
|
||||
url, err := url.Parse(addrToConnectTo)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// indicate that we've completed successfully, and that the caller
|
||||
// is responsible for calling cleanup
|
||||
callerWillCleanUp = true
|
||||
return url, cleanup
|
||||
}
|
||||
|
||||
|
@ -91,7 +177,7 @@ func TestBackendREST(t *testing.T) {
|
|||
defer cancel()
|
||||
|
||||
dir := rtest.TempDir(t)
|
||||
serverURL, cleanup := runRESTServer(ctx, t, dir)
|
||||
serverURL, cleanup := runRESTServer(ctx, t, dir, ":0")
|
||||
defer cleanup()
|
||||
|
||||
newTestSuite(serverURL, false).RunTests(t)
|
||||
|
@ -116,7 +202,7 @@ func BenchmarkBackendREST(t *testing.B) {
|
|||
defer cancel()
|
||||
|
||||
dir := rtest.TempDir(t)
|
||||
serverURL, cleanup := runRESTServer(ctx, t, dir)
|
||||
serverURL, cleanup := runRESTServer(ctx, t, dir, ":0")
|
||||
defer cleanup()
|
||||
|
||||
newTestSuite(serverURL, false).RunBenchmarks(t)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue