github.com/dolthub/dolt/go@v0.40.5-0.20240520175717-68db7794bea6/libraries/doltcore/remotesrv/grpc.go (about)

     1  // Copyright 2019 Dolthub, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package remotesrv
    16  
    17  import (
    18  	"context"
    19  	"encoding/base64"
    20  	"errors"
    21  	"fmt"
    22  	"io"
    23  	"net/url"
    24  	"path/filepath"
    25  	"strconv"
    26  	"strings"
    27  	"sync/atomic"
    28  
    29  	"github.com/sirupsen/logrus"
    30  	"google.golang.org/grpc/codes"
    31  	"google.golang.org/grpc/metadata"
    32  	"google.golang.org/grpc/status"
    33  
    34  	remotesapi "github.com/dolthub/dolt/go/gen/proto/dolt/services/remotesapi/v1alpha1"
    35  	"github.com/dolthub/dolt/go/libraries/doltcore/remotestorage"
    36  	"github.com/dolthub/dolt/go/libraries/utils/filesys"
    37  	"github.com/dolthub/dolt/go/store/chunks"
    38  	"github.com/dolthub/dolt/go/store/hash"
    39  	"github.com/dolthub/dolt/go/store/types"
    40  )
    41  
    42  var ErrUnimplemented = errors.New("unimplemented")
    43  
    44  const RepoPathField = "repo_path"
    45  
    46  type RemoteChunkStore struct {
    47  	HttpHost   string
    48  	httpScheme string
    49  
    50  	concurrencyControl remotesapi.PushConcurrencyControl
    51  
    52  	csCache DBCache
    53  	bucket  string
    54  	fs      filesys.Filesys
    55  	lgr     *logrus.Entry
    56  	sealer  Sealer
    57  	remotesapi.UnimplementedChunkStoreServiceServer
    58  }
    59  
    60  func NewHttpFSBackedChunkStore(lgr *logrus.Entry, httpHost string, csCache DBCache, fs filesys.Filesys, scheme string, concurrencyControl remotesapi.PushConcurrencyControl, sealer Sealer) *RemoteChunkStore {
    61  	if concurrencyControl == remotesapi.PushConcurrencyControl_PUSH_CONCURRENCY_CONTROL_UNSPECIFIED {
    62  		concurrencyControl = remotesapi.PushConcurrencyControl_PUSH_CONCURRENCY_CONTROL_IGNORE_WORKING_SET
    63  	}
    64  	return &RemoteChunkStore{
    65  		HttpHost:           httpHost,
    66  		httpScheme:         scheme,
    67  		concurrencyControl: concurrencyControl,
    68  		csCache:            csCache,
    69  		bucket:             "",
    70  		fs:                 fs,
    71  		lgr: lgr.WithFields(logrus.Fields{
    72  			"service": "dolt.services.remotesapi.v1alpha1.ChunkStoreServiceServer",
    73  		}),
    74  		sealer: sealer,
    75  	}
    76  }
    77  
    78  type repoRequest interface {
    79  	GetRepoId() *remotesapi.RepoId
    80  	GetRepoPath() string
    81  }
    82  
    83  func getRepoPath(req repoRequest) string {
    84  	if req.GetRepoPath() != "" {
    85  		return req.GetRepoPath()
    86  	}
    87  	if repoId := req.GetRepoId(); repoId != nil {
    88  		return repoId.Org + "/" + repoId.RepoName
    89  	}
    90  	panic("unexpected empty repo_path and nil repo_id")
    91  }
    92  
    93  func (rs *RemoteChunkStore) HasChunks(ctx context.Context, req *remotesapi.HasChunksRequest) (*remotesapi.HasChunksResponse, error) {
    94  	logger := getReqLogger(rs.lgr, "HasChunks")
    95  	if err := ValidateHasChunksRequest(req); err != nil {
    96  		return nil, status.Error(codes.InvalidArgument, err.Error())
    97  	}
    98  	repoPath := getRepoPath(req)
    99  	logger = logger.WithField(RepoPathField, repoPath)
   100  	defer func() { logger.Info("finished") }()
   101  
   102  	cs, err := rs.getStore(ctx, logger, repoPath)
   103  	if err != nil {
   104  		return nil, err
   105  	}
   106  
   107  	hashes, hashToIndex := remotestorage.ParseByteSlices(req.Hashes)
   108  
   109  	absent, err := cs.HasMany(ctx, hashes)
   110  	if err != nil {
   111  		logger.WithError(err).Error("error calling HasMany")
   112  		return nil, status.Error(codes.Internal, "HasMany failure:"+err.Error())
   113  	}
   114  
   115  	indices := make([]int32, len(absent))
   116  
   117  	n := 0
   118  	for h := range absent {
   119  		indices[n] = int32(hashToIndex[h])
   120  		n++
   121  	}
   122  
   123  	resp := &remotesapi.HasChunksResponse{
   124  		Absent: indices,
   125  	}
   126  
   127  	logger = logger.WithFields(logrus.Fields{
   128  		"num_requested": len(hashToIndex),
   129  		"num_absent":    len(indices),
   130  	})
   131  
   132  	return resp, nil
   133  }
   134  
   135  func (rs *RemoteChunkStore) getRelativeStorePath(cs RemoteSrvStore) (string, error) {
   136  	cspath, ok := cs.Path()
   137  	if !ok {
   138  		return "", status.Error(codes.Internal, "chunkstore misconfigured; cannot generate HTTP paths")
   139  	}
   140  	httproot, err := rs.fs.Abs(".")
   141  	if err != nil {
   142  		return "", err
   143  	}
   144  	prefix, err := filepath.Rel(httproot, cspath)
   145  	if err != nil {
   146  		return "", err
   147  	}
   148  	return prefix, nil
   149  }
   150  
   151  func (rs *RemoteChunkStore) GetDownloadLocations(ctx context.Context, req *remotesapi.GetDownloadLocsRequest) (*remotesapi.GetDownloadLocsResponse, error) {
   152  	logger := getReqLogger(rs.lgr, "GetDownloadLocations")
   153  	if err := ValidateGetDownloadLocsRequest(req); err != nil {
   154  		return nil, status.Error(codes.InvalidArgument, err.Error())
   155  	}
   156  	repoPath := getRepoPath(req)
   157  	logger = logger.WithField(RepoPathField, repoPath)
   158  	defer func() { logger.Info("finished") }()
   159  
   160  	cs, err := rs.getStore(ctx, logger, repoPath)
   161  	if err != nil {
   162  		return nil, err
   163  	}
   164  
   165  	hashes, _ := remotestorage.ParseByteSlices(req.ChunkHashes)
   166  
   167  	prefix, err := rs.getRelativeStorePath(cs)
   168  	if err != nil {
   169  		logger.WithError(err).Error("error getting file store path for chunk store")
   170  		return nil, err
   171  	}
   172  
   173  	numHashes := len(hashes)
   174  
   175  	locations, err := cs.GetChunkLocationsWithPaths(hashes)
   176  	if err != nil {
   177  		logger.WithError(err).Error("error getting chunk locations for hashes")
   178  		return nil, err
   179  	}
   180  
   181  	md, _ := metadata.FromIncomingContext(ctx)
   182  
   183  	var locs []*remotesapi.DownloadLoc
   184  	numRanges := 0
   185  	for loc, hashToRange := range locations {
   186  		if len(hashToRange) == 0 {
   187  			continue
   188  		}
   189  
   190  		numRanges += len(hashToRange)
   191  
   192  		var ranges []*remotesapi.RangeChunk
   193  		for h, r := range hashToRange {
   194  			hCpy := h
   195  			ranges = append(ranges, &remotesapi.RangeChunk{Hash: hCpy[:], Offset: r.Offset, Length: r.Length})
   196  		}
   197  
   198  		url := rs.getDownloadUrl(md, prefix+"/"+loc)
   199  		preurl := url.String()
   200  		url, err = rs.sealer.Seal(url)
   201  		if err != nil {
   202  			logger.WithError(err).Error("error sealing download url")
   203  			return nil, err
   204  		}
   205  		logger.WithFields(logrus.Fields{
   206  			"url":        preurl,
   207  			"ranges":     ranges,
   208  			"sealed_url": url.String(),
   209  		}).Trace("generated sealed url")
   210  
   211  		getRange := &remotesapi.HttpGetRange{Url: url.String(), Ranges: ranges}
   212  		locs = append(locs, &remotesapi.DownloadLoc{Location: &remotesapi.DownloadLoc_HttpGetRange{HttpGetRange: getRange}})
   213  	}
   214  
   215  	logger = logger.WithFields(logrus.Fields{
   216  		"num_requested": numHashes,
   217  		"num_urls":      len(locations),
   218  		"num_ranges":    numRanges,
   219  	})
   220  
   221  	return &remotesapi.GetDownloadLocsResponse{Locs: locs}, nil
   222  }
   223  
   224  func (rs *RemoteChunkStore) StreamDownloadLocations(stream remotesapi.ChunkStoreService_StreamDownloadLocationsServer) error {
   225  	ologger := getReqLogger(rs.lgr, "StreamDownloadLocations")
   226  	numMessages := 0
   227  	numHashes := 0
   228  	numUrls := 0
   229  	numRanges := 0
   230  	defer func() {
   231  		ologger.WithFields(logrus.Fields{
   232  			"num_messages":  numMessages,
   233  			"num_requested": numHashes,
   234  			"num_urls":      numUrls,
   235  			"num_ranges":    numRanges,
   236  		}).Info("finished")
   237  	}()
   238  	logger := ologger
   239  
   240  	md, _ := metadata.FromIncomingContext(stream.Context())
   241  
   242  	var repoPath string
   243  	var cs RemoteSrvStore
   244  	var prefix string
   245  	for {
   246  		req, err := stream.Recv()
   247  		if err != nil {
   248  			if err == io.EOF {
   249  				return nil
   250  			}
   251  			return err
   252  		}
   253  
   254  		numMessages += 1
   255  
   256  		if err := ValidateGetDownloadLocsRequest(req); err != nil {
   257  			return status.Error(codes.InvalidArgument, err.Error())
   258  		}
   259  
   260  		nextPath := getRepoPath(req)
   261  		if nextPath != repoPath {
   262  			repoPath = nextPath
   263  			logger = ologger.WithField(RepoPathField, repoPath)
   264  			cs, err = rs.getStore(stream.Context(), logger, repoPath)
   265  			if err != nil {
   266  				return err
   267  			}
   268  			prefix, err = rs.getRelativeStorePath(cs)
   269  			if err != nil {
   270  				logger.WithError(err).Error("error getting file store path for chunk store")
   271  				return err
   272  			}
   273  		}
   274  
   275  		hashes, _ := remotestorage.ParseByteSlices(req.ChunkHashes)
   276  		if err != nil {
   277  			return err
   278  		}
   279  		numHashes += len(hashes)
   280  		locations, err := cs.GetChunkLocationsWithPaths(hashes)
   281  		if err != nil {
   282  			logger.WithError(err).Error("error getting chunk locations for hashes")
   283  			return err
   284  		}
   285  
   286  		var locs []*remotesapi.DownloadLoc
   287  		for loc, hashToRange := range locations {
   288  			if len(hashToRange) == 0 {
   289  				continue
   290  			}
   291  
   292  			numUrls += 1
   293  			numRanges += len(hashToRange)
   294  
   295  			var ranges []*remotesapi.RangeChunk
   296  			for h, r := range hashToRange {
   297  				hCpy := h
   298  				ranges = append(ranges, &remotesapi.RangeChunk{Hash: hCpy[:], Offset: r.Offset, Length: r.Length})
   299  			}
   300  
   301  			url := rs.getDownloadUrl(md, prefix+"/"+loc)
   302  			preurl := url.String()
   303  			url, err = rs.sealer.Seal(url)
   304  			if err != nil {
   305  				logger.WithError(err).Error("error sealing download url")
   306  				return err
   307  			}
   308  			logger.WithFields(logrus.Fields{
   309  				"url":        preurl,
   310  				"ranges":     ranges,
   311  				"sealed_url": url.String(),
   312  			}).Trace("generated sealed url")
   313  
   314  			getRange := &remotesapi.HttpGetRange{Url: url.String(), Ranges: ranges}
   315  			locs = append(locs, &remotesapi.DownloadLoc{Location: &remotesapi.DownloadLoc_HttpGetRange{HttpGetRange: getRange}})
   316  		}
   317  
   318  		if err := stream.Send(&remotesapi.GetDownloadLocsResponse{Locs: locs}); err != nil {
   319  			return err
   320  		}
   321  	}
   322  }
   323  
   324  func (rs *RemoteChunkStore) getHost(md metadata.MD) string {
   325  	host := rs.HttpHost
   326  	if strings.HasPrefix(rs.HttpHost, ":") {
   327  		hosts := md.Get(":authority")
   328  		if len(hosts) > 0 {
   329  			host = strings.Split(hosts[0], ":")[0] + rs.HttpHost
   330  		}
   331  	} else if rs.HttpHost == "" {
   332  		hosts := md.Get(":authority")
   333  		if len(hosts) > 0 {
   334  			host = hosts[0]
   335  		}
   336  	}
   337  	return host
   338  }
   339  
   340  func (rs *RemoteChunkStore) getDownloadUrl(md metadata.MD, path string) *url.URL {
   341  	host := rs.getHost(md)
   342  	return &url.URL{
   343  		Scheme: rs.httpScheme,
   344  		Host:   host,
   345  		Path:   path,
   346  	}
   347  }
   348  
   349  func parseTableFileDetails(req *remotesapi.GetUploadLocsRequest) []*remotesapi.TableFileDetails {
   350  	tfd := req.GetTableFileDetails()
   351  
   352  	if len(tfd) == 0 {
   353  		_, hashToIdx := remotestorage.ParseByteSlices(req.TableFileHashes)
   354  
   355  		tfd = make([]*remotesapi.TableFileDetails, len(hashToIdx))
   356  		for h, i := range hashToIdx {
   357  			tfd[i] = &remotesapi.TableFileDetails{
   358  				Id:            h[:],
   359  				ContentLength: 0,
   360  				ContentHash:   nil,
   361  			}
   362  		}
   363  	}
   364  
   365  	return tfd
   366  }
   367  
   368  func (rs *RemoteChunkStore) GetUploadLocations(ctx context.Context, req *remotesapi.GetUploadLocsRequest) (*remotesapi.GetUploadLocsResponse, error) {
   369  	logger := getReqLogger(rs.lgr, "GetUploadLocations")
   370  	if err := ValidateGetUploadLocsRequest(req); err != nil {
   371  		return nil, status.Error(codes.InvalidArgument, err.Error())
   372  	}
   373  	repoPath := getRepoPath(req)
   374  	logger = logger.WithField(RepoPathField, repoPath)
   375  	defer func() { logger.Info("finished") }()
   376  
   377  	_, err := rs.getStore(ctx, logger, repoPath)
   378  	if err != nil {
   379  		return nil, err
   380  	}
   381  
   382  	tfds := parseTableFileDetails(req)
   383  
   384  	md, _ := metadata.FromIncomingContext(ctx)
   385  
   386  	var locs []*remotesapi.UploadLoc
   387  	for _, tfd := range tfds {
   388  		h := hash.New(tfd.Id)
   389  		url := rs.getUploadUrl(md, repoPath, tfd)
   390  		url, err = rs.sealer.Seal(url)
   391  		if err != nil {
   392  			logger.WithError(err).Error("error sealing upload url")
   393  			return nil, status.Error(codes.Internal, "Failed to seal upload Url.")
   394  		}
   395  
   396  		loc := &remotesapi.UploadLoc_HttpPost{HttpPost: &remotesapi.HttpPostTableFile{Url: url.String()}}
   397  		locs = append(locs, &remotesapi.UploadLoc{TableFileHash: h[:], Location: loc})
   398  
   399  		logger.WithFields(logrus.Fields{
   400  			"table_file_hash": h.String(),
   401  			"url":             url.String(),
   402  		}).Trace("sending upload location for table file")
   403  	}
   404  
   405  	logger = logger.WithFields(logrus.Fields{
   406  		"num_urls": len(locs),
   407  	})
   408  
   409  	return &remotesapi.GetUploadLocsResponse{Locs: locs}, nil
   410  }
   411  
   412  func (rs *RemoteChunkStore) getUploadUrl(md metadata.MD, repoPath string, tfd *remotesapi.TableFileDetails) *url.URL {
   413  	fileID := hash.New(tfd.Id).String()
   414  	params := url.Values{}
   415  	params.Add("num_chunks", strconv.Itoa(int(tfd.NumChunks)))
   416  	params.Add("content_length", strconv.Itoa(int(tfd.ContentLength)))
   417  	params.Add("content_hash", base64.RawURLEncoding.EncodeToString(tfd.ContentHash))
   418  	return &url.URL{
   419  		Scheme:   rs.httpScheme,
   420  		Host:     rs.getHost(md),
   421  		Path:     fmt.Sprintf("%s/%s", repoPath, fileID),
   422  		RawQuery: params.Encode(),
   423  	}
   424  }
   425  
   426  func (rs *RemoteChunkStore) Rebase(ctx context.Context, req *remotesapi.RebaseRequest) (*remotesapi.RebaseResponse, error) {
   427  	logger := getReqLogger(rs.lgr, "Rebase")
   428  	if err := ValidateRebaseRequest(req); err != nil {
   429  		return nil, status.Error(codes.InvalidArgument, err.Error())
   430  	}
   431  	repoPath := getRepoPath(req)
   432  	logger = logger.WithField(RepoPathField, repoPath)
   433  	defer func() { logger.Info("finished") }()
   434  
   435  	_, err := rs.getStore(ctx, logger, repoPath)
   436  	if err != nil {
   437  		return nil, err
   438  	}
   439  
   440  	return &remotesapi.RebaseResponse{}, nil
   441  }
   442  
   443  func (rs *RemoteChunkStore) Root(ctx context.Context, req *remotesapi.RootRequest) (*remotesapi.RootResponse, error) {
   444  	logger := getReqLogger(rs.lgr, "Root")
   445  	if err := ValidateRootRequest(req); err != nil {
   446  		return nil, status.Error(codes.InvalidArgument, err.Error())
   447  	}
   448  	repoPath := getRepoPath(req)
   449  	logger = logger.WithField(RepoPathField, repoPath)
   450  	defer func() { logger.Info("finished") }()
   451  
   452  	cs, err := rs.getStore(ctx, logger, repoPath)
   453  	if err != nil {
   454  		return nil, err
   455  	}
   456  
   457  	h, err := cs.Root(ctx)
   458  	if err != nil {
   459  		logger.WithError(err).Error("error calling Root on chunk store.")
   460  		return nil, status.Error(codes.Internal, "Failed to get root")
   461  	}
   462  
   463  	return &remotesapi.RootResponse{RootHash: h[:]}, nil
   464  }
   465  
   466  func (rs *RemoteChunkStore) Commit(ctx context.Context, req *remotesapi.CommitRequest) (*remotesapi.CommitResponse, error) {
   467  	logger := getReqLogger(rs.lgr, "Commit")
   468  	if err := ValidateCommitRequest(req); err != nil {
   469  		return nil, status.Error(codes.InvalidArgument, err.Error())
   470  	}
   471  	repoPath := getRepoPath(req)
   472  	logger = logger.WithField(RepoPathField, repoPath)
   473  	defer func() { logger.Info("finished") }()
   474  
   475  	cs, err := rs.getStore(ctx, logger, repoPath)
   476  	if err != nil {
   477  		return nil, err
   478  	}
   479  
   480  	updates := make(map[string]int)
   481  	for _, cti := range req.ChunkTableInfo {
   482  		updates[hash.New(cti.Hash).String()] = int(cti.ChunkCount)
   483  	}
   484  
   485  	err = cs.AddTableFilesToManifest(ctx, updates)
   486  	if err != nil {
   487  		logger.WithError(err).Error("error calling AddTableFilesToManifest")
   488  		return nil, status.Errorf(codes.Internal, "manifest update error: %v", err)
   489  	}
   490  
   491  	currHash := hash.New(req.Current)
   492  	lastHash := hash.New(req.Last)
   493  
   494  	var ok bool
   495  	ok, err = cs.Commit(ctx, currHash, lastHash)
   496  	if err != nil {
   497  		logger.WithError(err).WithFields(logrus.Fields{
   498  			"last_hash": lastHash.String(),
   499  			"curr_hash": currHash.String(),
   500  		}).Error("error calling Commit")
   501  		return nil, status.Errorf(codes.Internal, "failed to commit: %v", err)
   502  	}
   503  
   504  	logger.Tracef("Commit success; moved from %s -> %s", lastHash.String(), currHash.String())
   505  	return &remotesapi.CommitResponse{Success: ok}, nil
   506  }
   507  
   508  func (rs *RemoteChunkStore) GetRepoMetadata(ctx context.Context, req *remotesapi.GetRepoMetadataRequest) (*remotesapi.GetRepoMetadataResponse, error) {
   509  	logger := getReqLogger(rs.lgr, "GetRepoMetadata")
   510  	if err := ValidateGetRepoMetadataRequest(req); err != nil {
   511  		return nil, status.Error(codes.InvalidArgument, err.Error())
   512  	}
   513  
   514  	repoPath := getRepoPath(req)
   515  	logger = logger.WithField(RepoPathField, repoPath)
   516  	defer func() { logger.Info("finished") }()
   517  
   518  	cs, err := rs.getOrCreateStore(ctx, logger, repoPath, req.ClientRepoFormat.NbfVersion)
   519  	if err != nil {
   520  		return nil, err
   521  	}
   522  
   523  	size, err := cs.Size(ctx)
   524  	if err != nil {
   525  		logger.WithError(err).Error("error calling Size")
   526  		return nil, err
   527  	}
   528  
   529  	return &remotesapi.GetRepoMetadataResponse{
   530  		NbfVersion:             cs.Version(),
   531  		NbsVersion:             req.ClientRepoFormat.NbsVersion,
   532  		StorageSize:            size,
   533  		PushConcurrencyControl: rs.concurrencyControl,
   534  	}, nil
   535  }
   536  
   537  func (rs *RemoteChunkStore) ListTableFiles(ctx context.Context, req *remotesapi.ListTableFilesRequest) (*remotesapi.ListTableFilesResponse, error) {
   538  	logger := getReqLogger(rs.lgr, "ListTableFiles")
   539  	if err := ValidateListTableFilesRequest(req); err != nil {
   540  		return nil, status.Error(codes.InvalidArgument, err.Error())
   541  	}
   542  	repoPath := getRepoPath(req)
   543  	logger = logger.WithField(RepoPathField, repoPath)
   544  	defer func() { logger.Info("finished") }()
   545  
   546  	cs, err := rs.getStore(ctx, logger, repoPath)
   547  	if err != nil {
   548  		return nil, err
   549  	}
   550  
   551  	root, tables, appendixTables, err := cs.Sources(ctx)
   552  	if err != nil {
   553  		logger.WithError(err).Error("error getting chunk store Sources")
   554  		return nil, status.Error(codes.Internal, "failed to get sources")
   555  	}
   556  
   557  	md, _ := metadata.FromIncomingContext(ctx)
   558  
   559  	tableFileInfo, err := getTableFileInfo(logger, md, rs, tables, req, cs)
   560  	if err != nil {
   561  		logger.WithError(err).Error("error getting table file info")
   562  		return nil, err
   563  	}
   564  
   565  	appendixTableFileInfo, err := getTableFileInfo(logger, md, rs, appendixTables, req, cs)
   566  	if err != nil {
   567  		logger.WithError(err).Error("error getting appendix table file info")
   568  		return nil, err
   569  	}
   570  
   571  	logger = logger.WithFields(logrus.Fields{
   572  		"num_table_files":          len(tableFileInfo),
   573  		"num_appendix_table_files": len(appendixTableFileInfo),
   574  	})
   575  
   576  	resp := &remotesapi.ListTableFilesResponse{
   577  		RootHash:              root[:],
   578  		TableFileInfo:         tableFileInfo,
   579  		AppendixTableFileInfo: appendixTableFileInfo,
   580  	}
   581  
   582  	return resp, nil
   583  }
   584  
   585  func getTableFileInfo(
   586  	logger *logrus.Entry,
   587  	md metadata.MD,
   588  	rs *RemoteChunkStore,
   589  	tableList []chunks.TableFile,
   590  	req *remotesapi.ListTableFilesRequest,
   591  	cs RemoteSrvStore,
   592  ) ([]*remotesapi.TableFileInfo, error) {
   593  	prefix, err := rs.getRelativeStorePath(cs)
   594  	if err != nil {
   595  		return nil, err
   596  	}
   597  	appendixTableFileInfo := make([]*remotesapi.TableFileInfo, 0)
   598  	for _, t := range tableList {
   599  		url := rs.getDownloadUrl(md, prefix+"/"+t.LocationPrefix()+t.FileID())
   600  		url, err = rs.sealer.Seal(url)
   601  		if err != nil {
   602  			return nil, status.Error(codes.Internal, "failed to get seal download url for "+t.FileID())
   603  		}
   604  
   605  		appendixTableFileInfo = append(appendixTableFileInfo, &remotesapi.TableFileInfo{
   606  			FileId:    t.FileID(),
   607  			NumChunks: uint32(t.NumChunks()),
   608  			Url:       url.String(),
   609  		})
   610  	}
   611  	return appendixTableFileInfo, nil
   612  }
   613  
   614  // AddTableFiles updates the remote manifest with new table files without modifying the root hash.
   615  func (rs *RemoteChunkStore) AddTableFiles(ctx context.Context, req *remotesapi.AddTableFilesRequest) (*remotesapi.AddTableFilesResponse, error) {
   616  	logger := getReqLogger(rs.lgr, "AddTableFiles")
   617  	if err := ValidateAddTableFilesRequest(req); err != nil {
   618  		return nil, status.Error(codes.InvalidArgument, err.Error())
   619  	}
   620  	repoPath := getRepoPath(req)
   621  	logger = logger.WithField(RepoPathField, repoPath)
   622  	defer func() { logger.Info("finished") }()
   623  
   624  	cs, err := rs.getStore(ctx, logger, repoPath)
   625  	if err != nil {
   626  		return nil, err
   627  	}
   628  
   629  	updates := make(map[string]int)
   630  	for _, cti := range req.ChunkTableInfo {
   631  		updates[hash.New(cti.Hash).String()] = int(cti.ChunkCount)
   632  	}
   633  
   634  	err = cs.AddTableFilesToManifest(ctx, updates)
   635  	if err != nil {
   636  		logger.WithError(err).Error("error occurred updating the manifest")
   637  		return nil, status.Error(codes.Internal, "manifest update error")
   638  	}
   639  
   640  	logger = logger.WithFields(logrus.Fields{
   641  		"num_files": len(updates),
   642  	})
   643  
   644  	return &remotesapi.AddTableFilesResponse{Success: true}, nil
   645  }
   646  
   647  func (rs *RemoteChunkStore) getStore(ctx context.Context, logger *logrus.Entry, repoPath string) (RemoteSrvStore, error) {
   648  	return rs.getOrCreateStore(ctx, logger, repoPath, types.Format_Default.VersionString())
   649  }
   650  
   651  func (rs *RemoteChunkStore) getOrCreateStore(ctx context.Context, logger *logrus.Entry, repoPath, nbfVerStr string) (RemoteSrvStore, error) {
   652  	cs, err := rs.csCache.Get(ctx, repoPath, nbfVerStr)
   653  	if err != nil {
   654  		logger.WithError(err).Error("Failed to retrieve chunkstore")
   655  		if errors.Is(err, ErrUnimplemented) {
   656  			return nil, status.Error(codes.Unimplemented, err.Error())
   657  		}
   658  		return nil, err
   659  	}
   660  	if cs == nil {
   661  		logger.Error("internal error getting chunk store; csCache.Get returned nil")
   662  		return nil, status.Error(codes.Internal, "Could not get chunkstore")
   663  	}
   664  	return cs, nil
   665  }
   666  
   667  var requestId int32
   668  
   669  func incReqId() int {
   670  	return int(atomic.AddInt32(&requestId, 1))
   671  }
   672  
   673  func getReqLogger(lgr *logrus.Entry, method string) *logrus.Entry {
   674  	lgr = lgr.WithFields(logrus.Fields{
   675  		"method":      method,
   676  		"request_num": strconv.Itoa(incReqId()),
   677  	})
   678  	lgr.Info("starting request")
   679  	return lgr
   680  }
   681  
   682  type ReadOnlyChunkStore struct {
   683  	remotesapi.ChunkStoreServiceServer
   684  }
   685  
   686  func (rs ReadOnlyChunkStore) GetUploadLocations(ctx context.Context, req *remotesapi.GetUploadLocsRequest) (*remotesapi.GetUploadLocsResponse, error) {
   687  	return nil, status.Error(codes.PermissionDenied, "this server only provides read-only access")
   688  }
   689  
   690  func (rs ReadOnlyChunkStore) AddTableFiles(ctx context.Context, req *remotesapi.AddTableFilesRequest) (*remotesapi.AddTableFilesResponse, error) {
   691  	return nil, status.Error(codes.PermissionDenied, "this server only provides read-only access")
   692  }
   693  
   694  func (rs ReadOnlyChunkStore) Commit(ctx context.Context, req *remotesapi.CommitRequest) (*remotesapi.CommitResponse, error) {
   695  	return nil, status.Error(codes.PermissionDenied, "this server only provides read-only access")
   696  }