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
path: root/cmd
diff options
context:
space:
mode:
authorPatrick Steinhardt <psteinhardt@gitlab.com>2021-11-30 17:59:58 +0300
committerPatrick Steinhardt <psteinhardt@gitlab.com>2021-11-30 17:59:58 +0300
commitc023d0aaa1691096ddd1bd782af428ba5dfb19c8 (patch)
tree1f2132edd3a47f8991db4c63be2852997f988843 /cmd
parent9a53844de5035a693534d17715dcb77b95f7bf16 (diff)
parent64ad803a11f85abbf055114f795c8fd1bdfe0e08 (diff)
Merge branch 'smh-generalize-dataloss' into 'master'
Add 'praefect metadata' subcommand Closes #3481 See merge request gitlab-org/gitaly!4122
Diffstat (limited to 'cmd')
-rw-r--r--cmd/praefect/subcmd.go1
-rw-r--r--cmd/praefect/subcmd_metadata.go107
-rw-r--r--cmd/praefect/subcmd_metadata_test.go125
3 files changed, 233 insertions, 0 deletions
diff --git a/cmd/praefect/subcmd.go b/cmd/praefect/subcmd.go
index 2e966e809..af1502f31 100644
--- a/cmd/praefect/subcmd.go
+++ b/cmd/praefect/subcmd.go
@@ -48,6 +48,7 @@ var subcommands = map[string]subcmd{
praefect.NewGitalyNodeConnectivityCheck,
praefect.NewPostgresReadWriteCheck,
),
+ metadataCmdName: newMetadataSubcommand(os.Stdout),
}
// subCommand returns an exit code, to be fed into os.Exit.
diff --git a/cmd/praefect/subcmd_metadata.go b/cmd/praefect/subcmd_metadata.go
new file mode 100644
index 000000000..e913a0eec
--- /dev/null
+++ b/cmd/praefect/subcmd_metadata.go
@@ -0,0 +1,107 @@
+package main
+
+import (
+ "context"
+ "errors"
+ "flag"
+ "fmt"
+ "io"
+
+ "gitlab.com/gitlab-org/gitaly/v14/internal/praefect/config"
+ "gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb"
+)
+
+const metadataCmdName = "metadata"
+
+type metadataSubcommand struct {
+ stdout io.Writer
+ repositoryID int64
+ virtualStorage string
+ relativePath string
+}
+
+func newMetadataSubcommand(stdout io.Writer) *metadataSubcommand {
+ return &metadataSubcommand{stdout: stdout}
+}
+
+func (cmd *metadataSubcommand) FlagSet() *flag.FlagSet {
+ fs := flag.NewFlagSet(metadataCmdName, flag.ContinueOnError)
+ fs.Int64Var(&cmd.repositoryID, "repository-id", 0, "the repository's ID")
+ fs.StringVar(&cmd.virtualStorage, "virtual-storage", "", "the repository's virtual storage")
+ fs.StringVar(&cmd.relativePath, "relative-path", "", "the repository's relative path in the virtual storage")
+ return fs
+}
+
+func (cmd *metadataSubcommand) println(format string, args ...interface{}) {
+ fmt.Fprintf(cmd.stdout, format+"\n", args...)
+}
+
+func (cmd *metadataSubcommand) Exec(flags *flag.FlagSet, cfg config.Config) error {
+ if flags.NArg() > 0 {
+ return unexpectedPositionalArgsError{Command: flags.Name()}
+ }
+
+ var request gitalypb.GetRepositoryMetadataRequest
+ switch {
+ case cmd.repositoryID != 0:
+ if cmd.virtualStorage != "" || cmd.relativePath != "" {
+ return errors.New("virtual storage and relative path can't be provided with a repository ID")
+ }
+ request.Query = &gitalypb.GetRepositoryMetadataRequest_RepositoryId{RepositoryId: cmd.repositoryID}
+ case cmd.virtualStorage != "" || cmd.relativePath != "":
+ if cmd.virtualStorage == "" {
+ return errors.New("virtual storage is required with relative path")
+ } else if cmd.relativePath == "" {
+ return errors.New("relative path is required with virtual storage")
+ }
+ request.Query = &gitalypb.GetRepositoryMetadataRequest_Path_{
+ Path: &gitalypb.GetRepositoryMetadataRequest_Path{
+ VirtualStorage: cmd.virtualStorage,
+ RelativePath: cmd.relativePath,
+ },
+ }
+ default:
+ return errors.New("repository id or virtual storage and relative path required")
+ }
+
+ nodeAddr, err := getNodeAddress(cfg)
+ if err != nil {
+ return fmt.Errorf("get node address: %w", err)
+ }
+
+ ctx := context.TODO()
+ conn, err := subCmdDial(ctx, nodeAddr, cfg.Auth.Token, defaultDialTimeout)
+ if err != nil {
+ return fmt.Errorf("dial: %w", err)
+ }
+ defer conn.Close()
+
+ metadata, err := gitalypb.NewPraefectInfoServiceClient(conn).GetRepositoryMetadata(ctx, &request)
+ if err != nil {
+ return fmt.Errorf("get metadata: %w", err)
+ }
+
+ cmd.println("Repository ID: %d", metadata.RepositoryId)
+ cmd.println("Virtual Storage: %q", metadata.VirtualStorage)
+ cmd.println("Relative Path: %q", metadata.RelativePath)
+ cmd.println("Replica Path: %q", metadata.ReplicaPath)
+ cmd.println("Primary: %q", metadata.Primary)
+ cmd.println("Generation: %d", metadata.Generation)
+ cmd.println("Replicas:")
+ for _, replica := range metadata.Replicas {
+ cmd.println("- Storage: %q", replica.Storage)
+ cmd.println(" Assigned: %v", replica.Assigned)
+
+ generationText := fmt.Sprintf("%d, fully up to date", replica.Generation)
+ if replica.Generation == -1 {
+ generationText = "replica not yet created"
+ } else if replica.Generation < metadata.Generation {
+ generationText = fmt.Sprintf("%d, behind by %d changes", replica.Generation, metadata.Generation-replica.Generation)
+ }
+
+ cmd.println(" Generation: %s", generationText)
+ cmd.println(" Healthy: %v", replica.Healthy)
+ cmd.println(" Valid Primary: %v", replica.ValidPrimary)
+ }
+ return nil
+}
diff --git a/cmd/praefect/subcmd_metadata_test.go b/cmd/praefect/subcmd_metadata_test.go
new file mode 100644
index 000000000..d0bba3ec4
--- /dev/null
+++ b/cmd/praefect/subcmd_metadata_test.go
@@ -0,0 +1,125 @@
+package main
+
+import (
+ "bytes"
+ "errors"
+ "fmt"
+ "testing"
+
+ "github.com/stretchr/testify/require"
+ "gitlab.com/gitlab-org/gitaly/v14/internal/praefect/config"
+ "gitlab.com/gitlab-org/gitaly/v14/internal/praefect/datastore"
+ "gitlab.com/gitlab-org/gitaly/v14/internal/praefect/datastore/glsql"
+ "gitlab.com/gitlab-org/gitaly/v14/internal/praefect/service/info"
+ "gitlab.com/gitlab-org/gitaly/v14/internal/testhelper"
+ "gitlab.com/gitlab-org/gitaly/v14/internal/testhelper/testassert"
+ "gitlab.com/gitlab-org/gitaly/v14/internal/testhelper/testdb"
+ "google.golang.org/grpc/codes"
+ "google.golang.org/grpc/status"
+)
+
+func TestMetadataSubcommand(t *testing.T) {
+ t.Parallel()
+
+ ctx, cancel := testhelper.Context()
+ defer cancel()
+
+ tx := glsql.NewDB(t).Begin(t)
+ defer tx.Rollback(t)
+
+ testdb.SetHealthyNodes(t, ctx, tx, map[string]map[string][]string{
+ "praefect": {"virtual-storage": {"primary", "secondary-1"}},
+ })
+
+ rs := datastore.NewPostgresRepositoryStore(tx, map[string][]string{
+ "virtual-storage": {"primary", "secondary-1", "secondary-2"},
+ })
+ require.NoError(t, rs.CreateRepository(ctx, 1, "virtual-storage", "relative-path", "replica-path", "primary", []string{"secondary-1"}, []string{"secondary-2"}, true, true))
+ require.NoError(t, rs.IncrementGeneration(ctx, 1, "primary", nil))
+
+ ln, clean := listenAndServe(t, []svcRegistrar{
+ registerPraefectInfoServer(info.NewServer(config.Config{}, rs, nil, nil, nil)),
+ })
+ defer clean()
+
+ for _, tc := range []struct {
+ desc string
+ args []string
+ error error
+ }{
+ {
+ desc: "missing parameters fails",
+ error: errors.New("repository id or virtual storage and relative path required"),
+ },
+ {
+ desc: "repository id with virtual storage fails",
+ args: []string{"-repository-id=1", "-virtual-storage=virtual-storage"},
+ error: errors.New("virtual storage and relative path can't be provided with a repository ID"),
+ },
+ {
+ desc: "repository id with relative path fails",
+ args: []string{"-repository-id=1", "-relative-path=relative-path"},
+ error: errors.New("virtual storage and relative path can't be provided with a repository ID"),
+ },
+ {
+ desc: "virtual storage without relative path fails",
+ args: []string{"-virtual-storage=virtual-storage"},
+ error: errors.New("relative path is required with virtual storage"),
+ },
+ {
+ desc: "relative path without virtual storage fails",
+ args: []string{"-relative-path=relative-path"},
+ error: errors.New("virtual storage is required with relative path"),
+ },
+ {
+ desc: "repository not found",
+ args: []string{"-repository-id=2"},
+ error: fmt.Errorf("get metadata: %w", status.Error(codes.NotFound, "repository not found")),
+ },
+ {
+ desc: "repository found with repository id",
+ args: []string{"-repository-id=1"},
+ },
+ {
+ desc: "repository found with virtual storage and relative path",
+ args: []string{"-virtual-storage=virtual-storage", "-relative-path=relative-path"},
+ },
+ } {
+ t.Run(tc.desc, func(t *testing.T) {
+ stdout := &bytes.Buffer{}
+ cmd := newMetadataSubcommand(stdout)
+
+ fs := cmd.FlagSet()
+ require.NoError(t, fs.Parse(tc.args))
+ err := cmd.Exec(fs, config.Config{SocketPath: ln.Addr().String()})
+ testassert.GrpcEqualErr(t, tc.error, err)
+ if tc.error != nil {
+ return
+ }
+
+ require.Equal(t, `Repository ID: 1
+Virtual Storage: "virtual-storage"
+Relative Path: "relative-path"
+Replica Path: "replica-path"
+Primary: "primary"
+Generation: 1
+Replicas:
+- Storage: "primary"
+ Assigned: true
+ Generation: 1, fully up to date
+ Healthy: true
+ Valid Primary: true
+- Storage: "secondary-1"
+ Assigned: true
+ Generation: 0, behind by 1 changes
+ Healthy: true
+ Valid Primary: false
+- Storage: "secondary-2"
+ Assigned: true
+ Generation: replica not yet created
+ Healthy: false
+ Valid Primary: false
+`, stdout.String())
+ })
+ }
+}