Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitaly.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Okstad <pokstad@gitlab.com>2020-02-21 18:13:13 +0300
committerPaul Okstad <pokstad@gitlab.com>2020-02-21 18:13:13 +0300
commitde2413f70181a4ee3ce47b54ec6c848a9b5ddc77 (patch)
tree76bbaced34959630bb334990c6ea7eddc28da7d0
parent14a29e8aa33a0067486baeb69ab6d072c7ddcf19 (diff)
parent43f93995df4c5cd8bc5ffcac2ebf2d2a24cdc32c (diff)
Merge branch 'jv-clone-analyze-structured' into 'master'
Prepare 'gitaly-debug analyze-http-clone' for prometheus See merge request gitlab-org/gitaly!1555
-rw-r--r--cmd/gitaly-debug/analyzehttp.go261
-rw-r--r--internal/git/stats/analyzehttp.go392
-rw-r--r--internal/git/stats/analyzehttp_test.go97
-rw-r--r--internal/service/repository/create_from_url_test.go36
-rw-r--r--internal/testhelper/githttp.go40
5 files changed, 578 insertions, 248 deletions
diff --git a/cmd/gitaly-debug/analyzehttp.go b/cmd/gitaly-debug/analyzehttp.go
index 9a2749fc7..61cf0ce2e 100644
--- a/cmd/gitaly-debug/analyzehttp.go
+++ b/cmd/gitaly-debug/analyzehttp.go
@@ -1,243 +1,64 @@
package main
import (
- "bytes"
- "compress/gzip"
+ "context"
"fmt"
- "net/http"
- "os"
- "strings"
- "time"
- "gitlab.com/gitlab-org/gitaly/internal/git/pktline"
+ "gitlab.com/gitlab-org/gitaly/internal/git/stats"
)
func analyzeHTTPClone(cloneURL string) {
- wants := doBenchGet(cloneURL)
- doBenchPost(cloneURL, wants)
-}
-
-func doBenchGet(cloneURL string) []string {
- req, err := http.NewRequest("GET", cloneURL+"/info/refs?service=git-upload-pack", nil)
- noError(err)
-
- for k, v := range map[string]string{
- "User-Agent": "gitaly-debug",
- "Accept": "*/*",
- "Accept-Encoding": "deflate, gzip",
- "Pragma": "no-cache",
- } {
- req.Header.Set(k, v)
- }
-
- start := time.Now()
- msg("---")
- msg("--- GET %v", req.URL)
- msg("---")
- resp, err := http.DefaultClient.Do(req)
- noError(err)
-
- msg("response after %v", time.Since(start))
- msg("response header: %v", resp.Header)
- msg("HTTP status code %d", resp.StatusCode)
- defer resp.Body.Close()
-
- body := resp.Body
- if resp.Header.Get("Content-Encoding") == "gzip" {
- body, err = gzip.NewReader(body)
- noError(err)
- }
-
- // Expected response:
- // - "# service=git-upload-pack\n"
- // - FLUSH
- // - "<OID> <ref> <capabilities>\n"
- // - "<OID> <ref>\n"
- // - ...
- // - FLUSH
- //
- var wants []string
- var size int64
- seenFlush := false
- scanner := pktline.NewScanner(body)
- packets := 0
- refs := 0
- for ; scanner.Scan(); packets++ {
- if seenFlush {
- fatal("received packet after flush")
- }
-
- data := string(pktline.Data(scanner.Bytes()))
- size += int64(len(data))
- switch packets {
- case 0:
- msg("first packet %v", time.Since(start))
- if data != "# service=git-upload-pack\n" {
- fatal(fmt.Errorf("unexpected header %q", data))
- }
- case 1:
- if !pktline.IsFlush(scanner.Bytes()) {
- fatal("missing flush after service announcement")
- }
- default:
- if packets == 2 && !strings.Contains(data, " side-band-64k") {
- fatal(fmt.Errorf("missing side-band-64k capability in %q", data))
- }
-
- if pktline.IsFlush(scanner.Bytes()) {
- seenFlush = true
- continue
- }
-
- split := strings.SplitN(data, " ", 2)
- if len(split) != 2 {
- continue
- }
- refs++
-
- if strings.HasPrefix(split[1], "refs/heads/") || strings.HasPrefix(split[1], "refs/tags/") {
- wants = append(wants, split[0])
- }
- }
- }
- noError(scanner.Err())
- if !seenFlush {
- fatal("missing flush in response")
+ st := &stats.Clone{
+ URL: cloneURL,
+ Interactive: true,
}
- msg("received %d packets", packets)
- msg("done in %v", time.Since(start))
- msg("payload data: %d bytes", size)
- msg("received %d refs, selected %d wants", refs, len(wants))
-
- return wants
-}
-
-func doBenchPost(cloneURL string, wants []string) {
- reqBodyRaw := &bytes.Buffer{}
- reqBodyGzip := gzip.NewWriter(reqBodyRaw)
- for i, oid := range wants {
- if i == 0 {
- oid += " multi_ack_detailed no-done side-band-64k thin-pack ofs-delta deepen-since deepen-not agent=git/2.21.0"
- }
- _, err := pktline.WriteString(reqBodyGzip, "want "+oid+"\n")
- noError(err)
+ noError(st.Perform(context.Background()))
+
+ fmt.Println("\n--- GET metrics:")
+ for _, entry := range []metric{
+ {"response header time", st.Get.ResponseHeader()},
+ {"first Git packet", st.Get.FirstGitPacket()},
+ {"response body time", st.Get.ResponseBody()},
+ {"payload size", st.Get.PayloadSize()},
+ {"Git packets received", st.Get.Packets()},
+ {"refs advertised", st.Get.RefsAdvertised()},
+ {"wanted refs", st.RefsWanted()},
+ } {
+ entry.print()
}
- noError(pktline.WriteFlush(reqBodyGzip))
- _, err := pktline.WriteString(reqBodyGzip, "done\n")
- noError(err)
- noError(reqBodyGzip.Close())
-
- req, err := http.NewRequest("POST", cloneURL+"/git-upload-pack", reqBodyRaw)
- noError(err)
- for k, v := range map[string]string{
- "User-Agent": "gitaly-debug",
- "Content-Type": "application/x-git-upload-pack-request",
- "Accept": "application/x-git-upload-pack-result",
- "Content-Encoding": "gzip",
+ fmt.Println("\n--- POST metrics:")
+ for _, entry := range []metric{
+ {"response header time", st.Post.ResponseHeader()},
+ {"time to server NAK", st.Post.NAK()},
+ {"response body time", st.Post.ResponseBody()},
+ {"largest single Git packet", st.Post.LargestPacketSize()},
+ {"Git packets received", st.Post.Packets()},
} {
- req.Header.Set(k, v)
+ entry.print()
}
- start := time.Now()
- msg("---")
- msg("--- POST %v", req.URL)
- msg("---")
- resp, err := http.DefaultClient.Do(req)
- noError(err)
-
- msg("response after %v", time.Since(start))
- msg("response header: %v", resp.Header)
- msg("HTTP status code %d", resp.StatusCode)
- defer resp.Body.Close()
-
- // Expected response:
- // - "NAK\n"
- // - "<side band byte><pack or progress or error data>
- // - ...
- // - FLUSH
- //
- packets := 0
- scanner := pktline.NewScanner(resp.Body)
- totalSize := make(map[byte]int64)
- payloadSizeHistogram := make(map[int]int)
- sideBandHistogram := make(map[byte]int)
- seenFlush := false
- for ; scanner.Scan(); packets++ {
- if seenFlush {
- fatal("received extra packet after flush")
- }
-
- data := pktline.Data(scanner.Bytes())
-
- if packets == 0 {
- if !bytes.Equal([]byte("NAK\n"), data) {
- fatal(fmt.Errorf("expected NAK, got %q", data))
- }
- msg("received NAK after %v", time.Since(start))
- continue
- }
-
- if pktline.IsFlush(scanner.Bytes()) {
- seenFlush = true
+ for _, band := range stats.Bands() {
+ numPackets := st.Post.BandPackets(band)
+ if numPackets == 0 {
continue
}
- if len(data) == 0 {
- fatal("empty packet in PACK data")
- }
-
- band := data[0]
- if band < 1 || band > 3 {
- fatal(fmt.Errorf("invalid sideband: %d", band))
- }
- if sideBandHistogram[band] == 0 {
- msg("received first %s packet after %v", bandToHuman(band), time.Since(start))
- }
-
- sideBandHistogram[band]++
-
- // Print progress data as-is
- if band == 2 {
- _, err := os.Stdout.Write(data[1:])
- noError(err)
- }
-
- n := len(data[1:])
- totalSize[band] += int64(n)
- payloadSizeHistogram[n]++
-
- if packets%100 == 0 && packets > 0 && band == 1 {
- fmt.Printf(".")
+ fmt.Printf("\n--- POST %s band\n", band)
+ for _, entry := range []metric{
+ {"time to first packet", st.Post.BandFirstPacket(band)},
+ {"packets", numPackets},
+ {"total payload size", st.Post.BandPayloadSize(band)},
+ } {
+ entry.print()
}
}
-
- fmt.Println("") // Trailing newline for progress dots.
-
- noError(scanner.Err())
- if !seenFlush {
- fatal("POST response did not end in flush")
- }
-
- msg("received %d packets", packets)
- msg("done in %v", time.Since(start))
- for i := byte(1); i <= 3; i++ {
- msg("%8s band: %10d payload bytes, %6d packets", bandToHuman(i), totalSize[i], sideBandHistogram[i])
- }
- msg("packet payload size histogram: %v", payloadSizeHistogram)
}
-func bandToHuman(b byte) string {
- switch b {
- case 1:
- return "pack"
- case 2:
- return "progress"
- case 3:
- return "error"
- default:
- fatal(fmt.Errorf("invalid band %d", b))
- return "" // never reached
- }
+type metric struct {
+ key string
+ value interface{}
}
+
+func (m metric) print() { fmt.Printf("%-40s %v\n", m.key, m.value) }
diff --git a/internal/git/stats/analyzehttp.go b/internal/git/stats/analyzehttp.go
new file mode 100644
index 000000000..5fb87349a
--- /dev/null
+++ b/internal/git/stats/analyzehttp.go
@@ -0,0 +1,392 @@
+package stats
+
+import (
+ "bytes"
+ "compress/gzip"
+ "context"
+ "errors"
+ "fmt"
+ "net/http"
+ "os"
+ "strings"
+ "time"
+
+ "gitlab.com/gitlab-org/gitaly/internal/git/pktline"
+)
+
+type Clone struct {
+ URL string
+ Interactive bool
+
+ wants []string // all branch and tag pointers
+ Get
+ Post
+}
+
+func (cl *Clone) RefsWanted() int { return len(cl.wants) }
+
+// Perform does a Git HTTP clone, discarding cloned data to /dev/null.
+func (cl *Clone) Perform(ctx context.Context) error {
+ if err := cl.doGet(ctx); err != nil {
+ return ctxErr(ctx, err)
+ }
+
+ if err := cl.doPost(ctx); err != nil {
+ return ctxErr(ctx, err)
+ }
+
+ return nil
+}
+
+func ctxErr(ctx context.Context, err error) error {
+ if ctx.Err() != nil {
+ return ctx.Err()
+ }
+ return err
+}
+
+type Get struct {
+ start time.Time
+ responseHeader time.Duration
+ httpStatus int
+ firstGitPacket time.Duration
+ responseBody time.Duration
+ payloadSize int64
+ packets int
+ refs int
+}
+
+func (g *Get) ResponseHeader() time.Duration { return g.responseHeader }
+func (g *Get) HTTPStatus() int { return g.httpStatus }
+func (g *Get) FirstGitPacket() time.Duration { return g.firstGitPacket }
+func (g *Get) ResponseBody() time.Duration { return g.responseBody }
+func (g *Get) PayloadSize() int64 { return g.payloadSize }
+func (g *Get) Packets() int { return g.packets }
+func (g *Get) RefsAdvertised() int { return g.refs }
+
+func (cl *Clone) doGet(ctx context.Context) error {
+ req, err := http.NewRequest("GET", cl.URL+"/info/refs?service=git-upload-pack", nil)
+ if err != nil {
+ return err
+ }
+
+ req = req.WithContext(ctx)
+
+ for k, v := range map[string]string{
+ "User-Agent": "gitaly-debug",
+ "Accept": "*/*",
+ "Accept-Encoding": "deflate, gzip",
+ "Pragma": "no-cache",
+ } {
+ req.Header.Set(k, v)
+ }
+
+ cl.Get.start = time.Now()
+ cl.printInteractive("---")
+ cl.printInteractive("--- GET %v", req.URL)
+ cl.printInteractive("---")
+
+ resp, err := http.DefaultClient.Do(req)
+ if err != nil {
+ return err
+ }
+ defer resp.Body.Close()
+
+ cl.Get.responseHeader = time.Since(cl.Get.start)
+ cl.Get.httpStatus = resp.StatusCode
+ cl.printInteractive("response code: %d", resp.StatusCode)
+ cl.printInteractive("response header: %v", resp.Header)
+
+ body := resp.Body
+ if resp.Header.Get("Content-Encoding") == "gzip" {
+ body, err = gzip.NewReader(body)
+ if err != nil {
+ return err
+ }
+ }
+
+ // Expected response:
+ // - "# service=git-upload-pack\n"
+ // - FLUSH
+ // - "<OID> <ref> <capabilities>\n"
+ // - "<OID> <ref>\n"
+ // - ...
+ // - FLUSH
+ //
+ seenFlush := false
+ scanner := pktline.NewScanner(body)
+ for ; scanner.Scan(); cl.Get.packets++ {
+ if seenFlush {
+ return errors.New("received packet after flush")
+ }
+
+ data := string(pktline.Data(scanner.Bytes()))
+ cl.Get.payloadSize += int64(len(data))
+ switch cl.Get.packets {
+ case 0:
+ cl.Get.firstGitPacket = time.Since(cl.Get.start)
+
+ if data != "# service=git-upload-pack\n" {
+ return fmt.Errorf("unexpected header %q", data)
+ }
+ case 1:
+ if !pktline.IsFlush(scanner.Bytes()) {
+ return errors.New("missing flush after service announcement")
+ }
+ case 2:
+ if !strings.Contains(data, " side-band-64k") {
+ return fmt.Errorf("missing side-band-64k capability in %q", data)
+ }
+
+ fallthrough
+ default:
+ if pktline.IsFlush(scanner.Bytes()) {
+ seenFlush = true
+ continue
+ }
+
+ split := strings.SplitN(data, " ", 2)
+ if len(split) != 2 {
+ continue
+ }
+ cl.Get.refs++
+
+ if strings.HasPrefix(split[1], "refs/heads/") || strings.HasPrefix(split[1], "refs/tags/") {
+ cl.wants = append(cl.wants, split[0])
+ }
+ }
+ }
+ if err := scanner.Err(); err != nil {
+ return err
+ }
+ if !seenFlush {
+ return errors.New("missing flush in response")
+ }
+
+ cl.Get.responseBody = time.Since(cl.Get.start)
+
+ return nil
+}
+
+type Post struct {
+ start time.Time
+ responseHeader time.Duration
+ httpStatus int
+ nak time.Duration
+ multiband map[string]*bandInfo
+ responseBody time.Duration
+ packets int
+ largestPacketSize int
+}
+
+func (p *Post) ResponseHeader() time.Duration { return p.responseHeader }
+func (p *Post) HTTPStatus() int { return p.httpStatus }
+func (p *Post) NAK() time.Duration { return p.nak }
+func (p *Post) ResponseBody() time.Duration { return p.responseBody }
+func (p *Post) Packets() int { return p.packets }
+func (p *Post) LargestPacketSize() int { return p.largestPacketSize }
+
+func (p *Post) BandPackets(b string) int { return p.multiband[b].packets }
+func (p *Post) BandPayloadSize(b string) int64 { return p.multiband[b].size }
+func (p *Post) BandFirstPacket(b string) time.Duration { return p.multiband[b].firstPacket }
+
+type bandInfo struct {
+ firstPacket time.Duration
+ size int64
+ packets int
+}
+
+func (bi *bandInfo) consume(start time.Time, data []byte) {
+ if bi.packets == 0 {
+ bi.firstPacket = time.Since(start)
+ }
+ bi.size += int64(len(data))
+ bi.packets++
+}
+
+// See
+// https://github.com/git/git/blob/v2.25.0/Documentation/technical/http-protocol.txt#L351
+// for background information.
+func (cl *Clone) buildPost(ctx context.Context) (*http.Request, error) {
+ reqBodyRaw := &bytes.Buffer{}
+ reqBodyGzip := gzip.NewWriter(reqBodyRaw)
+ for i, oid := range cl.wants {
+ if i == 0 {
+ oid += " multi_ack_detailed no-done side-band-64k thin-pack ofs-delta deepen-since deepen-not agent=git/2.21.0"
+ }
+ if _, err := pktline.WriteString(reqBodyGzip, "want "+oid+"\n"); err != nil {
+ return nil, err
+ }
+ }
+ if err := pktline.WriteFlush(reqBodyGzip); err != nil {
+ return nil, err
+ }
+ if _, err := pktline.WriteString(reqBodyGzip, "done\n"); err != nil {
+ return nil, err
+ }
+ if err := reqBodyGzip.Close(); err != nil {
+ return nil, err
+ }
+
+ req, err := http.NewRequest("POST", cl.URL+"/git-upload-pack", reqBodyRaw)
+ if err != nil {
+ return nil, err
+ }
+
+ req = req.WithContext(ctx)
+
+ for k, v := range map[string]string{
+ "User-Agent": "gitaly-debug",
+ "Content-Type": "application/x-git-upload-pack-request",
+ "Accept": "application/x-git-upload-pack-result",
+ "Content-Encoding": "gzip",
+ } {
+ req.Header.Set(k, v)
+ }
+
+ return req, nil
+}
+
+func (cl *Clone) doPost(ctx context.Context) error {
+ req, err := cl.buildPost(ctx)
+ if err != nil {
+ return err
+ }
+
+ cl.Post.start = time.Now()
+ cl.printInteractive("---")
+ cl.printInteractive("--- POST %v", req.URL)
+ cl.printInteractive("---")
+
+ resp, err := http.DefaultClient.Do(req)
+ if err != nil {
+ return err
+ }
+ defer resp.Body.Close()
+
+ cl.Post.responseHeader = time.Since(cl.Post.start)
+ cl.Post.httpStatus = resp.StatusCode
+ cl.printInteractive("response code: %d", resp.StatusCode)
+ cl.printInteractive("response header: %v", resp.Header)
+
+ // Expected response:
+ // - "NAK\n"
+ // - "<side band byte><pack or progress or error data>
+ // - ...
+ // - FLUSH
+ //
+
+ cl.Post.multiband = make(map[string]*bandInfo)
+ for _, band := range Bands() {
+ cl.Post.multiband[band] = &bandInfo{}
+ }
+
+ seenFlush := false
+
+ scanner := pktline.NewScanner(resp.Body)
+ for ; scanner.Scan(); cl.Post.packets++ {
+ if seenFlush {
+ return errors.New("received extra packet after flush")
+ }
+
+ if n := len(scanner.Bytes()); n > cl.Post.largestPacketSize {
+ cl.Post.largestPacketSize = n
+ }
+
+ data := pktline.Data(scanner.Bytes())
+
+ if cl.Post.packets == 0 {
+ // We're now looking at the first git packet sent by the server. The
+ // server must conclude the ref negotiation. Because we have not sent any
+ // "have" messages there is nothing to negotiate and the server should
+ // send a single NAK.
+ if !bytes.Equal([]byte("NAK\n"), data) {
+ return fmt.Errorf("expected NAK, got %q", data)
+ }
+ cl.Post.nak = time.Since(cl.Post.start)
+ continue
+ }
+
+ if pktline.IsFlush(scanner.Bytes()) {
+ seenFlush = true
+ continue
+ }
+
+ if len(data) == 0 {
+ return errors.New("empty packet in PACK data")
+ }
+
+ band, err := bandToHuman(data[0])
+ if err != nil {
+ return err
+ }
+
+ cl.Post.multiband[band].consume(cl.Post.start, data[1:])
+
+ // Print progress data as-is
+ if cl.Interactive && band == bandProgress {
+ if _, err := os.Stdout.Write(data[1:]); err != nil {
+ return err
+ }
+ }
+
+ if cl.Interactive && cl.Post.packets%500 == 0 && cl.Post.packets > 0 && band == bandPack {
+ // Print dots to have some sort of progress meter for the user in
+ // interactive mode. It's not accurate progress, but it shows that
+ // something is happening.
+ if _, err := fmt.Print("."); err != nil {
+ return err
+ }
+ }
+ }
+
+ if cl.Interactive {
+ // Trailing newline for progress dots.
+ if _, err := fmt.Println(""); err != nil {
+ return err
+ }
+ }
+
+ if err := scanner.Err(); err != nil {
+ return err
+ }
+ if !seenFlush {
+ return errors.New("POST response did not end in flush")
+ }
+
+ cl.Post.responseBody = time.Since(cl.Post.start)
+ return nil
+}
+
+func (cl *Clone) printInteractive(format string, a ...interface{}) error {
+ if !cl.Interactive {
+ return nil
+ }
+
+ if _, err := fmt.Println(fmt.Sprintf(format, a...)); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+const (
+ bandPack = "pack"
+ bandProgress = "progress"
+ bandError = "error"
+)
+
+// These bands map to magic numbers 1, 2, 3. See
+// https://git-scm.com/docs/protocol-capabilities/2.24.0#_side_band_side_band_64k
+func Bands() []string { return []string{bandPack, bandProgress, bandError} }
+
+func bandToHuman(b byte) (string, error) {
+ bands := Bands()
+
+ // Band index bytes are 1-indexed.
+ if b < 1 || int(b) > len(bands) {
+ return "", fmt.Errorf("invalid band index: %d", b)
+ }
+
+ return bands[b-1], nil
+}
diff --git a/internal/git/stats/analyzehttp_test.go b/internal/git/stats/analyzehttp_test.go
new file mode 100644
index 000000000..4b4e7ee24
--- /dev/null
+++ b/internal/git/stats/analyzehttp_test.go
@@ -0,0 +1,97 @@
+package stats
+
+import (
+ "fmt"
+ "path/filepath"
+ "testing"
+ "time"
+
+ "github.com/stretchr/testify/require"
+ "gitlab.com/gitlab-org/gitaly/internal/testhelper"
+)
+
+func TestClone(t *testing.T) {
+ ctx, cancel := testhelper.Context()
+ defer cancel()
+
+ _, repoPath, cleanup := testhelper.NewTestRepo(t)
+ defer cleanup()
+
+ serverPort, stopGitServer := testhelper.GitServer(t, repoPath, nil)
+ defer stopGitServer()
+
+ clone := Clone{URL: fmt.Sprintf("http://localhost:%d/%s", serverPort, filepath.Base(repoPath))}
+ require.NoError(t, clone.Perform(ctx), "perform analysis clone")
+
+ const expectedWants = 90 // based on contents of _support/gitlab-test.git-packed-refs
+ require.Greater(t, clone.RefsWanted(), expectedWants, "number of wanted refs")
+
+ require.Equal(t, 200, clone.Get.HTTPStatus(), "get status")
+ require.Greater(t, clone.Get.Packets(), 0, "number of get packets")
+ require.Greater(t, clone.Get.PayloadSize(), int64(0), "get payload size")
+
+ previousValue := time.Duration(0)
+ for _, m := range []struct {
+ desc string
+ value time.Duration
+ }{
+ {"time to receive response header", clone.Get.ResponseHeader()},
+ {"time to first packet", clone.Get.FirstGitPacket()},
+ {"time to receive response body", clone.Get.ResponseBody()},
+ } {
+ require.True(t, m.value > previousValue, "get: expect %s (%v) to be greater than previous value %v", m.desc, m.value, previousValue)
+ previousValue = m.value
+ }
+
+ require.Equal(t, 200, clone.Post.HTTPStatus(), "post status")
+ require.Greater(t, clone.Post.Packets(), 0, "number of post packets")
+
+ require.Greater(t, clone.Post.BandPackets("progress"), 0, "number of progress packets")
+ require.Greater(t, clone.Post.BandPackets("pack"), 0, "number of pack packets")
+
+ require.Greater(t, clone.Post.BandPayloadSize("progress"), int64(0), "progress payload bytes")
+ require.Greater(t, clone.Post.BandPayloadSize("pack"), int64(0), "pack payload bytes")
+
+ previousValue = time.Duration(0)
+ for _, m := range []struct {
+ desc string
+ value time.Duration
+ }{
+ {"time to receive response header", clone.Post.ResponseHeader()},
+ {"time to receive NAK", clone.Post.NAK()},
+ {"time to receive first progress message", clone.Post.BandFirstPacket("progress")},
+ {"time to receive first pack message", clone.Post.BandFirstPacket("pack")},
+ {"time to receive response body", clone.Post.ResponseBody()},
+ } {
+ require.True(t, m.value > previousValue, "post: expect %s (%v) to be greater than previous value %v", m.desc, m.value, previousValue)
+ previousValue = m.value
+ }
+}
+
+func TestBandToHuman(t *testing.T) {
+ testCases := []struct {
+ in byte
+ out string
+ fail bool
+ }{
+ {in: 0, fail: true},
+ {in: 1, out: "pack"},
+ {in: 2, out: "progress"},
+ {in: 3, out: "error"},
+ {in: 4, fail: true},
+ }
+
+ for _, tc := range testCases {
+ t.Run(fmt.Sprintf("band index %d", tc.in), func(t *testing.T) {
+ out, err := bandToHuman(tc.in)
+
+ if tc.fail {
+ require.Error(t, err)
+ return
+ }
+
+ require.NoError(t, err)
+ require.Equal(t, tc.out, out, "band name")
+ })
+ }
+}
diff --git a/internal/service/repository/create_from_url_test.go b/internal/service/repository/create_from_url_test.go
index 4c9cf1796..d3f45f672 100644
--- a/internal/service/repository/create_from_url_test.go
+++ b/internal/service/repository/create_from_url_test.go
@@ -4,16 +4,13 @@ import (
"encoding/base64"
"fmt"
"io/ioutil"
- "net"
"net/http"
- "net/http/cgi"
"os"
"path"
"path/filepath"
"testing"
"github.com/stretchr/testify/require"
- "gitlab.com/gitlab-org/gitaly/internal/config"
"gitlab.com/gitlab-org/gitaly/internal/helper"
"gitlab.com/gitlab-org/gitaly/internal/testhelper"
"gitlab.com/gitlab-org/gitaly/proto/go/gitalypb"
@@ -40,7 +37,9 @@ func TestSuccessfulCreateRepositoryFromURLRequest(t *testing.T) {
user := "username123"
password := "password321localhost"
- port := gitServerWithBasicAuth(t, user, password, testRepoPath)
+ port, stopGitServer := gitServerWithBasicAuth(t, user, password, testRepoPath)
+ defer stopGitServer()
+
url := fmt.Sprintf("http://%s:%s@localhost:%d/%s", user, password, port, filepath.Base(testRepoPath))
req := &gitalypb.CreateRepositoryFromURLRequest{
@@ -172,35 +171,16 @@ func TestPreventingRedirect(t *testing.T) {
require.Error(t, err)
}
-func gitServerWithBasicAuth(t testing.TB, user, pass, repoPath string) int {
- f, err := os.Create(filepath.Join(repoPath, "git-daemon-export-ok"))
- require.NoError(t, err)
- require.NoError(t, f.Close())
-
- listener, err := net.Listen("tcp", ":0")
- require.NoError(t, err)
-
- s := http.Server{
- Handler: basicAuthMiddleware(t, user, pass, &cgi.Handler{
- Path: config.Config.Git.BinPath,
- Dir: "/",
- Args: []string{"http-backend"},
- Env: []string{
- "GIT_PROJECT_ROOT=" + filepath.Dir(repoPath),
- },
- }),
- }
- go s.Serve(listener)
-
- return listener.Addr().(*net.TCPAddr).Port
+func gitServerWithBasicAuth(t testing.TB, user, pass, repoPath string) (int, func() error) {
+ return testhelper.GitServer(t, repoPath, basicAuthMiddleware(t, user, pass))
}
-func basicAuthMiddleware(t testing.TB, user, pass string, next http.Handler) http.Handler {
- return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+func basicAuthMiddleware(t testing.TB, user, pass string) func(http.ResponseWriter, *http.Request, http.Handler) {
+ return func(w http.ResponseWriter, r *http.Request, next http.Handler) {
authUser, authPass, ok := r.BasicAuth()
require.True(t, ok, "should contain basic auth")
require.Equal(t, user, authUser, "username should match")
require.Equal(t, pass, authPass, "password should match")
next.ServeHTTP(w, r)
- })
+ }
}
diff --git a/internal/testhelper/githttp.go b/internal/testhelper/githttp.go
new file mode 100644
index 000000000..0c1416978
--- /dev/null
+++ b/internal/testhelper/githttp.go
@@ -0,0 +1,40 @@
+package testhelper
+
+import (
+ "io/ioutil"
+ "net"
+ "net/http"
+ "net/http/cgi"
+ "path/filepath"
+ "testing"
+
+ "github.com/stretchr/testify/require"
+ "gitlab.com/gitlab-org/gitaly/internal/config"
+)
+
+func GitServer(t testing.TB, repoPath string, middleware func(http.ResponseWriter, *http.Request, http.Handler)) (int, func() error) {
+ require.NoError(t, ioutil.WriteFile(filepath.Join(repoPath, "git-daemon-export-ok"), nil, 0644))
+
+ listener, err := net.Listen("tcp", "127.0.0.1:0")
+ require.NoError(t, err)
+
+ gitHTTPBackend := &cgi.Handler{
+ Path: config.Config.Git.BinPath,
+ Dir: "/",
+ Args: []string{"http-backend"},
+ Env: []string{
+ "GIT_PROJECT_ROOT=" + filepath.Dir(repoPath),
+ },
+ }
+ s := http.Server{Handler: gitHTTPBackend}
+
+ if middleware != nil {
+ s.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ middleware(w, r, gitHTTPBackend)
+ })
+ }
+
+ go s.Serve(listener)
+
+ return listener.Addr().(*net.TCPAddr).Port, s.Close
+}