1package info
2
3import (
4	"context"
5	"fmt"
6
7	"gitlab.com/gitlab-org/gitaly/v14/internal/helper"
8	"gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb"
9	"golang.org/x/sync/errgroup"
10	"google.golang.org/grpc"
11)
12
13// RepositoryReplicas returns a list of repositories that includes the checksum of the primary as well as the replicas
14func (s *Server) RepositoryReplicas(ctx context.Context, in *gitalypb.RepositoryReplicasRequest) (*gitalypb.RepositoryReplicasResponse, error) {
15	virtualStorage := in.GetRepository().GetStorageName()
16	relativePath := in.GetRepository().GetRelativePath()
17
18	primary, err := s.primaryGetter.GetPrimary(ctx, virtualStorage, relativePath)
19	if err != nil {
20		return nil, fmt.Errorf("get primary: %w", err)
21	}
22
23	assignments, err := s.assignmentStore.GetHostAssignments(ctx, virtualStorage, relativePath)
24	if err != nil {
25		return nil, fmt.Errorf("get host assignments: %w", err)
26	}
27
28	secondaries := make([]string, 0, len(assignments)-1)
29	primaryIsAssigned := false
30	for _, assignment := range assignments {
31		if primary == assignment {
32			primaryIsAssigned = true
33			continue
34		}
35
36		secondaries = append(secondaries, assignment)
37	}
38
39	if !primaryIsAssigned {
40		return nil, fmt.Errorf("primary %q is not an assigned host", primary)
41	}
42
43	var resp gitalypb.RepositoryReplicasResponse
44
45	if resp.Primary, err = s.getRepositoryDetails(ctx, virtualStorage, primary, relativePath); err != nil {
46		return nil, helper.ErrInternal(err)
47	}
48
49	resp.Replicas = make([]*gitalypb.RepositoryReplicasResponse_RepositoryDetails, len(secondaries))
50
51	g, ctx := errgroup.WithContext(ctx)
52
53	for i, storage := range secondaries {
54		i := i             // rescoping
55		storage := storage // rescoping
56		g.Go(func() error {
57			var err error
58			resp.Replicas[i], err = s.getRepositoryDetails(ctx, virtualStorage, storage, relativePath)
59			return err
60		})
61	}
62
63	if err := g.Wait(); err != nil {
64		return nil, helper.ErrInternal(err)
65	}
66
67	return &resp, nil
68}
69
70func (s *Server) getRepositoryDetails(ctx context.Context, virtualStorage, storage, relativePath string) (*gitalypb.RepositoryReplicasResponse_RepositoryDetails, error) {
71	conn, ok := s.conns[virtualStorage][storage]
72	if !ok {
73		return nil, fmt.Errorf("no connection to %q/%q", virtualStorage, storage)
74	}
75
76	return getChecksum(
77		ctx,
78		&gitalypb.Repository{
79			StorageName:  storage,
80			RelativePath: relativePath,
81		}, conn)
82}
83
84func getChecksum(ctx context.Context, repo *gitalypb.Repository, cc *grpc.ClientConn) (*gitalypb.RepositoryReplicasResponse_RepositoryDetails, error) {
85	client := gitalypb.NewRepositoryServiceClient(cc)
86
87	resp, err := client.CalculateChecksum(ctx,
88		&gitalypb.CalculateChecksumRequest{
89			Repository: repo,
90		})
91	if err != nil {
92		return nil, err
93	}
94
95	return &gitalypb.RepositoryReplicasResponse_RepositoryDetails{
96		Repository: repo,
97		Checksum:   resp.GetChecksum(),
98	}, nil
99}
100