Commit 1ac5d04bdb
Verified · cmc
Layout: unified · split
internal/control/control.go +4
| @@ -14,6 +14,7 @@ import ( | ||
| 14 | 14 | "time" |
| 15 | 15 | |
| 16 | 16 | "gitbay.org/gitbay/internal/config" |
| 17 | "gitbay.org/gitbay/internal/packlimit" | |
| 17 | 18 | "gitbay.org/gitbay/internal/protocol" |
| 18 | 19 | "gitbay.org/gitbay/internal/store" |
| 19 | 20 | ) |
| @@ -67,6 +68,9 @@ type Ctx struct { | ||
| 67 | 68 | // restarting. It closes Done too; a command that ends on Done checks |
| 68 | 69 | // it to say why. |
| 69 | 70 | Stopping <-chan struct{} |
| 71 | // Packs is the pack-generation limiter a command that runs git to | |
| 72 | // produce an archive takes a slot from; nil is no limit. | |
| 73 | Packs *packlimit.Limiter | |
| 70 | 74 | } |
| 71 | 75 | |
| 72 | 76 | // SourceWeb is Ctx.Source for a request from a browser session. Its |
internal/control/download_test.go added +71
| @@ -0,0 +1,71 @@ | ||
| 1 | package control | |
| 2 | ||
| 3 | import ( | |
| 4 | "bytes" | |
| 5 | "strings" | |
| 6 | "testing" | |
| 7 | "time" | |
| 8 | ||
| 9 | "gitbay.org/gitbay/internal/packlimit" | |
| 10 | "gitbay.org/gitbay/internal/protocol" | |
| 11 | "gitbay.org/gitbay/internal/store" | |
| 12 | ) | |
| 13 | ||
| 14 | // repo download takes a pack slot: busy while the limiter is full, and | |
| 15 | // the slot is free again once git archive has exited. | |
| 16 | func TestRepoDownloadTakesAPackSlot(t *testing.T) { | |
| 17 | st, repo, root, _ := prunedRepo(t) | |
| 18 | alice, err := st.UserByUsername("alice") | |
| 19 | if err != nil { | |
| 20 | t.Fatal(err) | |
| 21 | } | |
| 22 | packs := packlimit.New(1, 0, 0, time.Second) | |
| 23 | hold, err := packs.Acquire(nil, "ip:elsewhere") | |
| 24 | if err != nil { | |
| 25 | t.Fatal(err) | |
| 26 | } | |
| 27 | c, errOut := pruneCtx(st, root, alice) | |
| 28 | c.Packs = packs | |
| 29 | if code := Dispatch(c, []string{"repo", "download", repo.Path()}); code != protocol.ExitFailure || | |
| 30 | !strings.Contains(errOut.String(), "limit of concurrent clones") { | |
| 31 | t.Fatalf("busy: exit %d: %q", code, errOut.String()) | |
| 32 | } | |
| 33 | hold() | |
| 34 | ||
| 35 | c, errOut = pruneCtx(st, root, alice) | |
| 36 | c.Packs = packs | |
| 37 | if code := Dispatch(c, []string{"repo", "download", repo.Path()}); code != protocol.ExitOK { | |
| 38 | t.Fatalf("free: exit %d: %s", code, errOut.String()) | |
| 39 | } | |
| 40 | if c.Stdout.(*bytes.Buffer).Len() == 0 { | |
| 41 | t.Fatal("no archive written") | |
| 42 | } | |
| 43 | hold, err = packs.Acquire(nil, "ip:elsewhere") | |
| 44 | if err != nil { | |
| 45 | t.Fatalf("slot not released after the download: %v", err) | |
| 46 | } | |
| 47 | hold() | |
| 48 | } | |
| 49 | ||
| 50 | // A download the caller may not make is refused before the limiter. | |
| 51 | func TestRepoDownloadRefusalStaysOffLimiter(t *testing.T) { | |
| 52 | st, repo, root, _ := prunedRepo(t) | |
| 53 | if err := st.SetRepoVisibility(repo.ID, "private"); err != nil { | |
| 54 | t.Fatal(err) | |
| 55 | } | |
| 56 | bobID, err := st.CreateUser("bob", false) | |
| 57 | if err != nil { | |
| 58 | t.Fatal(err) | |
| 59 | } | |
| 60 | packs := packlimit.New(1, 0, 0, time.Second) | |
| 61 | hold, err := packs.Acquire(nil, "ip:elsewhere") | |
| 62 | if err != nil { | |
| 63 | t.Fatal(err) | |
| 64 | } | |
| 65 | defer hold() | |
| 66 | c, errOut := pruneCtx(st, root, store.User{ID: bobID, Username: "bob"}) | |
| 67 | c.Packs = packs | |
| 68 | if code := Dispatch(c, []string{"repo", "download", repo.Path()}); code != protocol.ExitNotFound { | |
| 69 | t.Fatalf("exit %d: %q", code, errOut.String()) | |
| 70 | } | |
| 71 | } | |
internal/control/explore.go +19
| @@ -1,10 +1,13 @@ | ||
| 1 | 1 | package control |
| 2 | 2 | |
| 3 | 3 | import ( |
| 4 | "errors" | |
| 4 | 5 | "io" |
| 6 | "strconv" | |
| 5 | 7 | "strings" |
| 6 | 8 | |
| 7 | 9 | "gitbay.org/gitbay/internal/gitutil" |
| 10 | "gitbay.org/gitbay/internal/packlimit" | |
| 8 | 11 | "gitbay.org/gitbay/internal/policy" |
| 9 | 12 | "gitbay.org/gitbay/internal/protocol" |
| 10 | 13 | ) |
| @@ -117,6 +120,22 @@ func runRepoDownload(c *Ctx, args []string) int { | ||
| 117 | 120 | // The prefix git puts on every path inside the archive, so unpacking |
| 118 | 121 | // lands in a named directory rather than the current one. |
| 119 | 122 | prefix := repo.Name + "-" + ref |
| 123 | // git archive draws on the same budget as clones and web archives, | |
| 124 | // counted against the account the way upload-pack is. | |
| 125 | principal := "user:" + strconv.FormatInt(c.User.ID, 10) | |
| 126 | release, err := c.Packs.Acquire(c.Done, principal) | |
| 127 | if err != nil { | |
| 128 | transport := "ssh" | |
| 129 | if c.ViaAPI { | |
| 130 | transport = "api" | |
| 131 | } | |
| 132 | c.Packs.Refused(transport, principal, err) | |
| 133 | if errors.Is(err, packlimit.ErrBusy) { | |
| 134 | return c.fail(protocol.ExitFailure, "the server is busy: it is at its limit of concurrent clones and fetches; try again in a minute") | |
| 135 | } | |
| 136 | return c.fail(protocol.ExitFailure, "the server is restarting; try again in a minute") | |
| 137 | } | |
| 138 | defer release() | |
| 120 | 139 | if err := gitutil.Archive(dir, ref, prefix, c.Stdout); err != nil { |
| 121 | 140 | return c.fail(protocol.ExitFailure, "%v", err) |
| 122 | 141 | } |
internal/httpd/api.go +1
| @@ -77,6 +77,7 @@ func (s *Server) apiCmd(w http.ResponseWriter, r *http.Request) { | ||
| 77 | 77 | Expires: tok.ExpiresAt, |
| 78 | 78 | Done: s.until(r), |
| 79 | 79 | Stopping: s.stopping, |
| 80 | Packs: s.packs, | |
| 80 | 81 | } |
| 81 | 82 | code := control.Dispatch(ctx, req.Argv) |
| 82 | 83 | |
internal/httpd/apiread.go +1
| @@ -64,6 +64,7 @@ func (s *Server) apiRead(w http.ResponseWriter, r *http.Request) { | ||
| 64 | 64 | ReadOnly: true, |
| 65 | 65 | Done: s.until(r), |
| 66 | 66 | Stopping: s.stopping, |
| 67 | Packs: s.packs, | |
| 67 | 68 | } |
| 68 | 69 | code := control.Dispatch(ctx, argv) |
| 69 | 70 | |
internal/sshd/sshd.go +1
| @@ -515,6 +515,7 @@ func Exec(cfg config.Config, st *store.Store, packs, pushes *packlimit.Limiter, | ||
| 515 | 515 | Done: done, |
| 516 | 516 | Stopping: stopping, |
| 517 | 517 | Expires: key.ExpiresAt, |
| 518 | Packs: packs, | |
| 518 | 519 | } |
| 519 | 520 | return control.Dispatch(ctx, argv) |
| 520 | 521 | } |