diff options
author | Patrick Steinhardt <psteinhardt@gitlab.com> | 2021-06-22 12:03:00 +0300 |
---|---|---|
committer | Patrick Steinhardt <psteinhardt@gitlab.com> | 2021-06-30 13:03:46 +0300 |
commit | 67783f933b9d9a9d5c7788311c5526f61e1d3a36 (patch) | |
tree | a3e5b3656d494098393f2b330ca4aaab1bddcf83 /internal/blackbox | |
parent | 4e36ad35d4c963f3210c3cd84b5b037b7aa192bf (diff) |
blackbox: Implement support for "push" probes
Implement support for "push" probes for gitaly-blackbox. These probes
take as input a set of commands which specify which references are to be
updated on the remote side as well as a packfile containing necessary
objects to complete the request.
The packfile is being provided by the user as a simple path name. While
this is a bit awkward given that the user thus has to pre-generate the
packfile and put it on disk somewhere, we cannot really assume that the
gitaly-blackbox host has a valid Git installation and repository. It's
thus not safe to say that we're able to generate the packfile on the
fly. So the only alternative to having a packfile on-disk is to manually
assemble the packfile via a reimplementation or to have a packfile as
static data. Neither of these options seem like the right thing to do,
so requiring pre-generated packfiles feels like the least-awful choice.
Note that the commands are static for each probe, which essentially
means that the same set of probes will be executed repeatedly. This
again is a bit awkward for the user: if executing the same mutating
operation multiple times, then latter executions behave differently
given that the target reference is likely already at the target
reference. While we could implement custom cleanup logic in
gitaly-blackbox which automatically removes or resets updated references
to their previous state again. For simplicity's sake, we instead put the
burden one the user for now: for each probe which does a change, the
user can implement a second probe which reverts the change again.
Together, this provides a very simple interface which, given a packfile
and a set of commands, updates remote references.
Changelog: added
Diffstat (limited to 'internal/blackbox')
-rw-r--r-- | internal/blackbox/blackbox.go | 44 | ||||
-rw-r--r-- | internal/blackbox/config.go | 34 |
2 files changed, 78 insertions, 0 deletions
diff --git a/internal/blackbox/blackbox.go b/internal/blackbox/blackbox.go index e9259a78e..5a77ff69d 100644 --- a/internal/blackbox/blackbox.go +++ b/internal/blackbox/blackbox.go @@ -4,10 +4,12 @@ import ( "context" "fmt" "net" + "os" "time" "github.com/prometheus/client_golang/prometheus" log "github.com/sirupsen/logrus" + "gitlab.com/gitlab-org/gitaly/v14/internal/git" "gitlab.com/gitlab-org/gitaly/v14/internal/git/stats" "gitlab.com/gitlab-org/gitaly/v14/internal/version" "gitlab.com/gitlab-org/labkit/monitoring" @@ -149,6 +151,8 @@ func (b Blackbox) runProbes() { switch probe.Type { case Fetch: err = b.fetch(probe) + case Push: + err = b.push(probe) default: err = fmt.Errorf("unsupported probe type: %q", probe.Type) } @@ -187,3 +191,43 @@ func (b Blackbox) fetch(probe Probe) error { return nil } + +func (b Blackbox) push(probe Probe) error { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + commands := make([]stats.PushCommand, len(probe.Push.Commands)) + for i, command := range probe.Push.Commands { + oldOID, err := git.NewObjectIDFromHex(command.OldOID) + if err != nil { + return fmt.Errorf("invalid old object ID for probe %q: %w", probe.Name, err) + } + + newOID, err := git.NewObjectIDFromHex(command.NewOID) + if err != nil { + return fmt.Errorf("invalid new object ID for probe %q: %w", probe.Name, err) + } + + commands[i] = stats.PushCommand{ + Reference: git.ReferenceName(command.Reference), + OldOID: oldOID, + NewOID: newOID, + } + } + + packfile, err := os.Open(probe.Push.Packfile) + if err != nil { + return fmt.Errorf("opening packfile for probe %q: %w", probe.Name, err) + } + defer packfile.Close() + + clone, err := stats.PerformHTTPPush( + ctx, probe.URL, probe.User, probe.Password, commands, packfile, false) + if err != nil { + return err + } + + b.httpPostMetrics.measure(probe.Name, &clone.SendPack) + + return nil +} diff --git a/internal/blackbox/config.go b/internal/blackbox/config.go index 8f966c67d..46b59809e 100644 --- a/internal/blackbox/config.go +++ b/internal/blackbox/config.go @@ -31,8 +31,29 @@ const ( // Fetch exercises the equivalent of git-fetch(1) with measurements for packfile negotiation // and receiving the packfile. Fetch = ProbeType("fetch") + // Push exercises the equivalent of git-push(1) with measurements for packfile negotiation + // and sending the packfile. + Push = ProbeType("push") ) +// PushCommand describes a command performed as part of the push. +type PushCommand struct { + // OldOID is the old state of the reference that should be updated. + OldOID string `toml:"old_oid"` + // OldOID is the new target state of the reference that should be updated. + NewOID string `toml:"new_oid"` + // Reference is the name of the reference that should be updated. + Reference string `toml:"reference"` +} + +// PushConfig is the configuration for a Push-type probe. +type PushConfig struct { + // Commands is the list of commands which should be executed as part of the push. + Commands []PushCommand `toml:"commands"` + // Packfile is the path to the packfile that shall be sent as part of the push. + Packfile string `toml:"packfile"` +} + // Probe is the configuration for a specific endpoint whose clone performance should be exercised. type Probe struct { // Name is the name of the probe. This is used both for logging and for exported @@ -49,6 +70,8 @@ type Probe struct { // Password is the password to authenticate with when connecting to the repository. // Note that this password may easily leak when connecting to a non-HTTPS URL. Password string `toml:"password"` + // Push contains the configuration of a Push-type probe. + Push *PushConfig `toml:"push"` } // ParseConfig parses the provided TOML-formatted configuration string and either returns the @@ -83,6 +106,17 @@ func ParseConfig(raw string) (Config, error) { switch probe.Type { case "", Fetch: probe.Type = Fetch + if probe.Push != nil { + return Config{}, fmt.Errorf("fetch probe %q cannot have push configuration", probe.Name) + } + case Push: + if probe.Push == nil { + return Config{}, fmt.Errorf("push probe %q must have push configuration", probe.Name) + } + + if len(probe.Push.Commands) == 0 { + return Config{}, fmt.Errorf("push probe %q must have at least one command", probe.Name) + } default: return Config{}, fmt.Errorf("unsupported probe type: %q", probe.Type) } |