diff options
author | Toon Claes <toon@gitlab.com> | 2023-10-20 18:57:57 +0300 |
---|---|---|
committer | Toon Claes <toon@gitlab.com> | 2023-11-20 12:45:29 +0300 |
commit | a4dc75466dfebf2e583ecbf692fa0856f9952401 (patch) | |
tree | 2e399f568bfde738604e96d5e238d9f7120eee0a | |
parent | 0540fb5062304efffc5291acc533438ab7b412ec (diff) |
smarthttp: Advertise bundle-uri capability
The first step toward using bundle-URI is making the server advertise it
supports this capability.
Insert uploadpack.advertiseBundleURIs in the git config so the info/refs
response contains the bundle-uri capability.
With this we also introduce the feature flag that allows us to toggle
the use of bundle-URIs.
-rw-r--r-- | internal/bundleuri/doc.go | 16 | ||||
-rw-r--r-- | internal/bundleuri/git_config.go | 27 | ||||
-rw-r--r-- | internal/bundleuri/testhelper_test.go | 11 | ||||
-rw-r--r-- | internal/featureflag/ff_bundle_uri.go | 9 | ||||
-rw-r--r-- | internal/gitaly/service/smarthttp/inforefs.go | 7 | ||||
-rw-r--r-- | internal/gitaly/service/smarthttp/inforefs_test.go | 100 |
6 files changed, 158 insertions, 12 deletions
diff --git a/internal/bundleuri/doc.go b/internal/bundleuri/doc.go new file mode 100644 index 000000000..22a0b4957 --- /dev/null +++ b/internal/bundleuri/doc.go @@ -0,0 +1,16 @@ +// Package bundleuri is used to enable the use [Bundle-URI] when the client +// clones/fetches from the repository. +// +// Bundle-URI is a concept in Git that allows the server to send one or more +// URIs where [git bundles] are available. The client can download such bundles +// to prepopulate the repository before it starts the object negotiation with +// the server. This reduces the CPU load on the server, and the amount of +// traffic that has to travel directly from server to client. +// +// This feature piggy-backs onto server-side backups. Refer to the +// [backup documentation] how to create and store bundles on a cloud provider. +// +// [Bundle-URI]: https://git-scm.com/docs/bundle-uri +// [git bundles]: https://git-scm.com/docs/git-bundle +// [backup documentation]: https://gitlab.com/gitlab-org/gitaly/-/blob/master/doc/gitaly-backup.md +package bundleuri diff --git a/internal/bundleuri/git_config.go b/internal/bundleuri/git_config.go new file mode 100644 index 000000000..2c6ab5c02 --- /dev/null +++ b/internal/bundleuri/git_config.go @@ -0,0 +1,27 @@ +package bundleuri + +import ( + "context" + + "gitlab.com/gitlab-org/gitaly/v16/internal/backup" + "gitlab.com/gitlab-org/gitaly/v16/internal/featureflag" + "gitlab.com/gitlab-org/gitaly/v16/internal/git" + "gitlab.com/gitlab-org/gitaly/v16/internal/gitaly/storage" +) + +// InfoRefsGitConfig return a slice of git.ConfigPairs you can inject into the +// call to git-upload-pack(1) --advertise-refs, to advertise the use of +// bundle-URI to the client who clones/fetches from the repository. +func InfoRefsGitConfig(ctx context.Context) []git.ConfigPair { + if featureflag.BundleURI.IsDisabled(ctx) { + return []git.ConfigPair{} + } + + return []git.ConfigPair{ + { + Key: "uploadpack.advertiseBundleURIs", + Value: "true", + }, + } +} + diff --git a/internal/bundleuri/testhelper_test.go b/internal/bundleuri/testhelper_test.go new file mode 100644 index 000000000..b2dd3cfea --- /dev/null +++ b/internal/bundleuri/testhelper_test.go @@ -0,0 +1,11 @@ +package bundleuri + +import ( + "testing" + + "gitlab.com/gitlab-org/gitaly/v16/internal/testhelper" +) + +func TestMain(m *testing.M) { + testhelper.Run(m) +} diff --git a/internal/featureflag/ff_bundle_uri.go b/internal/featureflag/ff_bundle_uri.go new file mode 100644 index 000000000..c912169dc --- /dev/null +++ b/internal/featureflag/ff_bundle_uri.go @@ -0,0 +1,9 @@ +package featureflag + +// BundleURI enables the use of git's bundle URI feature +var BundleURI = NewFeatureFlag( + "bundle_uri", + "v16.6.0", + "https://gitlab.com/gitlab-org/gitaly/-/issues/5656", + false, +) diff --git a/internal/gitaly/service/smarthttp/inforefs.go b/internal/gitaly/service/smarthttp/inforefs.go index 2a01868d7..63be73f0f 100644 --- a/internal/gitaly/service/smarthttp/inforefs.go +++ b/internal/gitaly/service/smarthttp/inforefs.go @@ -5,6 +5,7 @@ import ( "fmt" "io" + "gitlab.com/gitlab-org/gitaly/v16/internal/bundleuri" "gitlab.com/gitlab-org/gitaly/v16/internal/git" "gitlab.com/gitlab-org/gitaly/v16/internal/git/pktline" "gitlab.com/gitlab-org/gitaly/v16/internal/log" @@ -62,11 +63,13 @@ func (s *server) handleInfoRefs(ctx context.Context, service, repoPath string, r cmdOpts = append(cmdOpts, git.WithDisabledHooks()) } - config, err := git.ConvertConfigOptions(req.GitConfigOptions) + gitConfig, err := git.ConvertConfigOptions(req.GitConfigOptions) if err != nil { return err } - cmdOpts = append(cmdOpts, git.WithConfig(config...)) + gitConfig = append(gitConfig, bundleuri.InfoRefsGitConfig(ctx)...) + + cmdOpts = append(cmdOpts, git.WithConfig(gitConfig...)) if _, err := pktline.WriteString(w, fmt.Sprintf("# service=git-%s\n", service)); err != nil { return structerr.NewInternal("pktLine: %w", err) diff --git a/internal/gitaly/service/smarthttp/inforefs_test.go b/internal/gitaly/service/smarthttp/inforefs_test.go index ba458bac9..836dfce4a 100644 --- a/internal/gitaly/service/smarthttp/inforefs_test.go +++ b/internal/gitaly/service/smarthttp/inforefs_test.go @@ -33,7 +33,10 @@ import ( func TestInfoRefsUploadPack_successful(t *testing.T) { t.Parallel() - testhelper.NewFeatureSets(featureflag.UploadPackBoundaryBitmapTraversal).Run(t, testInfoRefsUploadPackSuccessful) + testhelper.NewFeatureSets( + featureflag.UploadPackBoundaryBitmapTraversal, + featureflag.BundleURI, + ).Run(t, testInfoRefsUploadPackSuccessful) } func testInfoRefsUploadPackSuccessful(t *testing.T, ctx context.Context) { @@ -63,7 +66,10 @@ func testInfoRefsUploadPackSuccessful(t *testing.T, ctx context.Context) { func TestInfoRefsUploadPack_internalRefs(t *testing.T) { t.Parallel() - testhelper.NewFeatureSets(featureflag.UploadPackBoundaryBitmapTraversal).Run(t, testInfoRefsUploadPackInternalRefs) + testhelper.NewFeatureSets( + featureflag.UploadPackBoundaryBitmapTraversal, + featureflag.BundleURI, + ).Run(t, testInfoRefsUploadPackInternalRefs) } func testInfoRefsUploadPackInternalRefs(t *testing.T, ctx context.Context) { @@ -138,7 +144,10 @@ func testInfoRefsUploadPackInternalRefs(t *testing.T, ctx context.Context) { func TestInfoRefsUploadPack_validate(t *testing.T) { t.Parallel() - testhelper.NewFeatureSets(featureflag.UploadPackBoundaryBitmapTraversal).Run(t, testInfoRefsUploadPackValidate) + testhelper.NewFeatureSets( + featureflag.UploadPackBoundaryBitmapTraversal, + featureflag.BundleURI, + ).Run(t, testInfoRefsUploadPackValidate) } func testInfoRefsUploadPackValidate(t *testing.T, ctx context.Context) { @@ -176,7 +185,10 @@ func testInfoRefsUploadPackValidate(t *testing.T, ctx context.Context) { func TestInfoRefsUploadPack_partialClone(t *testing.T) { t.Parallel() - testhelper.NewFeatureSets(featureflag.UploadPackBoundaryBitmapTraversal).Run(t, testInfoRefsUploadPackPartialClone) + testhelper.NewFeatureSets( + featureflag.UploadPackBoundaryBitmapTraversal, + featureflag.BundleURI, + ).Run(t, testInfoRefsUploadPackPartialClone) } func testInfoRefsUploadPackPartialClone(t *testing.T, ctx context.Context) { @@ -204,7 +216,10 @@ func testInfoRefsUploadPackPartialClone(t *testing.T, ctx context.Context) { func TestInfoRefsUploadPack_gitConfigOptions(t *testing.T) { t.Parallel() - testhelper.NewFeatureSets(featureflag.UploadPackBoundaryBitmapTraversal).Run(t, testInfoRefsUploadPackGitConfigOptions) + testhelper.NewFeatureSets( + featureflag.UploadPackBoundaryBitmapTraversal, + featureflag.BundleURI, + ).Run(t, testInfoRefsUploadPackGitConfigOptions) } func testInfoRefsUploadPackGitConfigOptions(t *testing.T, ctx context.Context) { @@ -230,10 +245,45 @@ func testInfoRefsUploadPackGitConfigOptions(t *testing.T, ctx context.Context) { }) } +func TestInfoRefsUploadPack_bundleURI(t *testing.T) { + t.Parallel() + + testhelper.NewFeatureSets( + featureflag.UploadPackBoundaryBitmapTraversal, + ).Run(t, testInfoRefsUploadPackBundleURI) +} + +func testInfoRefsUploadPackBundleURI(t *testing.T, ctx context.Context) { + t.Parallel() + + ctx = featureflag.OutgoingCtxWithFeatureFlag(ctx, featureflag.BundleURI, true) + + cfg := testcfg.Build(t) + cfg.SocketPath = runSmartHTTPServer(t, cfg) + + repo, repoPath := gittest.CreateRepository(t, ctx, cfg) + + gittest.WriteCommit(t, cfg, repoPath, gittest.WithBranch("main"), gittest.WithParents()) + + rpcRequest := &gitalypb.InfoRefsRequest{ + Repository: repo, + GitProtocol: git.ProtocolV2, + GitConfigOptions: []string{"transfer.bundleURI=true"}, + } + response, err := makeInfoRefsUploadPackRequest(t, ctx, cfg.SocketPath, cfg.Auth.Token, rpcRequest) + require.NoError(t, err) + requireAdvertisedCapabilitiesV2(t, string(response), "git-upload-pack", []string{ + "bundle-uri", + }) +} + func TestInfoRefsUploadPack_gitProtocol(t *testing.T) { t.Parallel() - testhelper.NewFeatureSets(featureflag.UploadPackBoundaryBitmapTraversal).Run(t, testInfoRefsUploadPackGitProtocol) + testhelper.NewFeatureSets( + featureflag.UploadPackBoundaryBitmapTraversal, + featureflag.BundleURI, + ).Run(t, testInfoRefsUploadPackGitProtocol) } func testInfoRefsUploadPackGitProtocol(t *testing.T, ctx context.Context) { @@ -290,7 +340,10 @@ func makeInfoRefsUploadPackRequest(t *testing.T, ctx context.Context, serverSock func TestInfoRefsReceivePack_successful(t *testing.T) { t.Parallel() - testhelper.NewFeatureSets(featureflag.UploadPackBoundaryBitmapTraversal).Run(t, testInfoRefsReceivePackSuccessful) + testhelper.NewFeatureSets( + featureflag.UploadPackBoundaryBitmapTraversal, + featureflag.BundleURI, + ).Run(t, testInfoRefsReceivePackSuccessful) } func testInfoRefsReceivePackSuccessful(t *testing.T, ctx context.Context) { @@ -322,7 +375,10 @@ func TestInfoRefsReceivePack_hiddenRefs(t *testing.T) { Object pools are not yet support with WAL. This test is testing with a pooled repository.`) t.Parallel() - testhelper.NewFeatureSets(featureflag.UploadPackBoundaryBitmapTraversal).Run(t, testInfoRefsReceivePackHiddenRefs) + testhelper.NewFeatureSets( + featureflag.UploadPackBoundaryBitmapTraversal, + featureflag.BundleURI, + ).Run(t, testInfoRefsReceivePackHiddenRefs) } func testInfoRefsReceivePackHiddenRefs(t *testing.T, ctx context.Context) { @@ -352,7 +408,10 @@ func testInfoRefsReceivePackHiddenRefs(t *testing.T, ctx context.Context) { func TestInfoRefsReceivePack_validate(t *testing.T) { t.Parallel() - testhelper.NewFeatureSets(featureflag.UploadPackBoundaryBitmapTraversal).Run(t, testInfoRefsReceivePackValidate) + testhelper.NewFeatureSets( + featureflag.UploadPackBoundaryBitmapTraversal, + featureflag.BundleURI, + ).Run(t, testInfoRefsReceivePackValidate) } func testInfoRefsReceivePackValidate(t *testing.T, ctx context.Context) { @@ -405,6 +464,24 @@ func makeInfoRefsReceivePackRequest(t *testing.T, ctx context.Context, serverSoc return response, err } +func requireAdvertisedCapabilitiesV2(t *testing.T, responseBody, expectedService string, expectedCapabilities []string) { + t.Helper() + + responseLines := strings.SplitAfter(responseBody, "\n") + require.Greater(t, len(responseLines), 2) + + // The first line contains the service announcement + require.Equal(t, gittest.Pktlinef(t, "# service=%s\n", expectedService), responseLines[0]) + + // The second line contains the protocol version + require.Equal(t, "0000"+gittest.Pktlinef(t, "version %d\n", 2), responseLines[1]) + + // The third line and following lines contain capabilities + for _, expectedCap := range expectedCapabilities { + require.Contains(t, responseLines[2:], gittest.Pktlinef(t, "%s\n", expectedCap)) + } +} + func requireAdvertisedRefs(t *testing.T, responseBody, expectedService string, expectedRefs []string) { t.Helper() @@ -444,7 +521,10 @@ func (ms *mockStreamer) PutStream(ctx context.Context, repo *gitalypb.Repository func TestInfoRefsUploadPack_cache(t *testing.T) { t.Parallel() - testhelper.NewFeatureSets(featureflag.UploadPackBoundaryBitmapTraversal).Run(t, testInfoRefsUploadPackCache) + testhelper.NewFeatureSets( + featureflag.UploadPackBoundaryBitmapTraversal, + featureflag.BundleURI, + ).Run(t, testInfoRefsUploadPackCache) } func testInfoRefsUploadPackCache(t *testing.T, ctx context.Context) { |