github.com/hasnat/dolt/go@v0.0.0-20210628190320-9eb5d843fbb7/utils/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 main
    16  
    17  import (
    18  	"context"
    19  	"fmt"
    20  	"io"
    21  	"log"
    22  	"os"
    23  	"path/filepath"
    24  	"sync/atomic"
    25  
    26  	"google.golang.org/grpc/codes"
    27  	"google.golang.org/grpc/status"
    28  	"google.golang.org/protobuf/proto"
    29  
    30  	remotesapi "github.com/dolthub/dolt/go/gen/proto/dolt/services/remotesapi/v1alpha1"
    31  	"github.com/dolthub/dolt/go/libraries/doltcore/remotestorage"
    32  	"github.com/dolthub/dolt/go/store/hash"
    33  	"github.com/dolthub/dolt/go/store/nbs"
    34  	"github.com/dolthub/dolt/go/store/types"
    35  )
    36  
    37  type RemoteChunkStore struct {
    38  	HttpHost string
    39  	csCache  *DBCache
    40  	bucket   string
    41  	remotesapi.UnimplementedChunkStoreServiceServer
    42  }
    43  
    44  func NewHttpFSBackedChunkStore(httpHost string, csCache *DBCache) *RemoteChunkStore {
    45  	return &RemoteChunkStore{
    46  		HttpHost: httpHost,
    47  		csCache:  csCache,
    48  		bucket:   "",
    49  	}
    50  }
    51  
    52  func (rs *RemoteChunkStore) HasChunks(ctx context.Context, req *remotesapi.HasChunksRequest) (*remotesapi.HasChunksResponse, error) {
    53  	logger := getReqLogger("GRPC", "HasChunks")
    54  	defer func() { logger("finished") }()
    55  
    56  	cs := rs.getStore(req.RepoId, "HasChunks")
    57  
    58  	if cs == nil {
    59  		return nil, status.Error(codes.Internal, "Could not get chunkstore")
    60  	}
    61  
    62  	logger(fmt.Sprintf("found repo %s/%s", req.RepoId.Org, req.RepoId.RepoName))
    63  
    64  	hashes, hashToIndex := remotestorage.ParseByteSlices(req.Hashes)
    65  
    66  	absent, err := cs.HasMany(ctx, hashes)
    67  
    68  	if err != nil {
    69  		return nil, status.Error(codes.Internal, "HasMany failure:"+err.Error())
    70  	}
    71  
    72  	indices := make([]int32, len(absent))
    73  
    74  	n := 0
    75  	for h := range absent {
    76  		indices[n] = int32(hashToIndex[h])
    77  		n++
    78  	}
    79  
    80  	//logger(fmt.Sprintf("missing chunks: %v", indices))
    81  
    82  	resp := &remotesapi.HasChunksResponse{
    83  		Absent: indices,
    84  	}
    85  
    86  	return resp, nil
    87  }
    88  
    89  func (rs *RemoteChunkStore) GetDownloadLocations(ctx context.Context, req *remotesapi.GetDownloadLocsRequest) (*remotesapi.GetDownloadLocsResponse, error) {
    90  	logger := getReqLogger("GRPC", "GetDownloadLocations")
    91  	defer func() { logger("finished") }()
    92  
    93  	cs := rs.getStore(req.RepoId, "GetDownloadLoctions")
    94  
    95  	if cs == nil {
    96  		return nil, status.Error(codes.Internal, "Could not get chunkstore")
    97  	}
    98  
    99  	logger(fmt.Sprintf("found repo %s/%s", req.RepoId.Org, req.RepoId.RepoName))
   100  
   101  	org := req.RepoId.Org
   102  	repoName := req.RepoId.RepoName
   103  	hashes, _ := remotestorage.ParseByteSlices(req.ChunkHashes)
   104  	locations, err := cs.GetChunkLocations(hashes)
   105  
   106  	if err != nil {
   107  		return nil, err
   108  	}
   109  
   110  	var locs []*remotesapi.DownloadLoc
   111  	for loc, hashToRange := range locations {
   112  		var ranges []*remotesapi.RangeChunk
   113  		for h, r := range hashToRange {
   114  			hCpy := h
   115  			ranges = append(ranges, &remotesapi.RangeChunk{Hash: hCpy[:], Offset: r.Offset, Length: r.Length})
   116  		}
   117  
   118  		url, err := rs.getDownloadUrl(logger, org, repoName, loc.String())
   119  		if err != nil {
   120  			log.Println("Failed to sign request", err)
   121  			return nil, err
   122  		}
   123  
   124  		logger("The URL is " + url)
   125  
   126  		getRange := &remotesapi.HttpGetRange{Url: url, Ranges: ranges}
   127  		locs = append(locs, &remotesapi.DownloadLoc{Location: &remotesapi.DownloadLoc_HttpGetRange{HttpGetRange: getRange}})
   128  	}
   129  
   130  	return &remotesapi.GetDownloadLocsResponse{Locs: locs}, nil
   131  }
   132  
   133  func (rs *RemoteChunkStore) StreamDownloadLocations(stream remotesapi.ChunkStoreService_StreamDownloadLocationsServer) error {
   134  	logger := getReqLogger("GRPC", "StreamDownloadLocations")
   135  	defer func() { logger("finished") }()
   136  
   137  	var repoID *remotesapi.RepoId
   138  	var cs *nbs.NomsBlockStore
   139  	for {
   140  		req, err := stream.Recv()
   141  		if err != nil {
   142  			if err == io.EOF {
   143  				return nil
   144  			}
   145  			return err
   146  		}
   147  
   148  		if !proto.Equal(req.RepoId, repoID) {
   149  			repoID = req.RepoId
   150  			cs = rs.getStore(repoID, "StreamDownloadLoctions")
   151  			if cs == nil {
   152  				return status.Error(codes.Internal, "Could not get chunkstore")
   153  			}
   154  			logger(fmt.Sprintf("found repo %s/%s", repoID.Org, repoID.RepoName))
   155  		}
   156  
   157  		org := req.RepoId.Org
   158  		repoName := req.RepoId.RepoName
   159  		hashes, _ := remotestorage.ParseByteSlices(req.ChunkHashes)
   160  		locations, err := cs.GetChunkLocations(hashes)
   161  		if err != nil {
   162  			return err
   163  		}
   164  
   165  		var locs []*remotesapi.DownloadLoc
   166  		for loc, hashToRange := range locations {
   167  			var ranges []*remotesapi.RangeChunk
   168  			for h, r := range hashToRange {
   169  				hCpy := h
   170  				ranges = append(ranges, &remotesapi.RangeChunk{Hash: hCpy[:], Offset: r.Offset, Length: r.Length})
   171  			}
   172  
   173  			url, err := rs.getDownloadUrl(logger, org, repoName, loc.String())
   174  			if err != nil {
   175  				log.Println("Failed to sign request", err)
   176  				return err
   177  			}
   178  
   179  			logger("The URL is " + url)
   180  
   181  			getRange := &remotesapi.HttpGetRange{Url: url, Ranges: ranges}
   182  			locs = append(locs, &remotesapi.DownloadLoc{Location: &remotesapi.DownloadLoc_HttpGetRange{HttpGetRange: getRange}})
   183  		}
   184  
   185  		if err := stream.Send(&remotesapi.GetDownloadLocsResponse{Locs: locs}); err != nil {
   186  			return err
   187  		}
   188  	}
   189  }
   190  
   191  func (rs *RemoteChunkStore) getDownloadUrl(logger func(string), org, repoName, fileId string) (string, error) {
   192  	return fmt.Sprintf("http://%s/%s/%s/%s", rs.HttpHost, org, repoName, fileId), nil
   193  }
   194  
   195  func parseTableFileDetails(req *remotesapi.GetUploadLocsRequest) []*remotesapi.TableFileDetails {
   196  	tfd := req.GetTableFileDetails()
   197  
   198  	if len(tfd) == 0 {
   199  		_, hashToIdx := remotestorage.ParseByteSlices(req.TableFileHashes)
   200  
   201  		tfd = make([]*remotesapi.TableFileDetails, len(hashToIdx))
   202  		for h, i := range hashToIdx {
   203  			tfd[i] = &remotesapi.TableFileDetails{
   204  				Id:            h[:],
   205  				ContentLength: 0,
   206  				ContentHash:   nil,
   207  			}
   208  		}
   209  	}
   210  
   211  	return tfd
   212  }
   213  
   214  func (rs *RemoteChunkStore) GetUploadLocations(ctx context.Context, req *remotesapi.GetUploadLocsRequest) (*remotesapi.GetUploadLocsResponse, error) {
   215  	logger := getReqLogger("GRPC", "GetUploadLocations")
   216  	defer func() { logger("finished") }()
   217  
   218  	cs := rs.getStore(req.RepoId, "GetWriteChunkUrls")
   219  
   220  	if cs == nil {
   221  		return nil, status.Error(codes.Internal, "Could not get chunkstore")
   222  	}
   223  
   224  	logger(fmt.Sprintf("found repo %s/%s", req.RepoId.Org, req.RepoId.RepoName))
   225  
   226  	org := req.RepoId.Org
   227  	repoName := req.RepoId.RepoName
   228  	tfds := parseTableFileDetails(req)
   229  
   230  	var locs []*remotesapi.UploadLoc
   231  	for _, tfd := range tfds {
   232  		h := hash.New(tfd.Id)
   233  		url, err := rs.getUploadUrl(logger, org, repoName, tfd)
   234  
   235  		if err != nil {
   236  			return nil, status.Error(codes.Internal, "Failed to get upload Url.")
   237  		}
   238  
   239  		loc := &remotesapi.UploadLoc_HttpPost{HttpPost: &remotesapi.HttpPostTableFile{Url: url}}
   240  		locs = append(locs, &remotesapi.UploadLoc{TableFileHash: h[:], Location: loc})
   241  
   242  		logger(fmt.Sprintf("sending upload location for chunk %s: %s", h.String(), url))
   243  	}
   244  
   245  	return &remotesapi.GetUploadLocsResponse{Locs: locs}, nil
   246  }
   247  
   248  func (rs *RemoteChunkStore) getUploadUrl(logger func(string), org, repoName string, tfd *remotesapi.TableFileDetails) (string, error) {
   249  	fileID := hash.New(tfd.Id).String()
   250  	expectedFiles[fileID] = tfd
   251  	return fmt.Sprintf("http://%s/%s/%s/%s", rs.HttpHost, org, repoName, fileID), nil
   252  }
   253  
   254  func (rs *RemoteChunkStore) Rebase(ctx context.Context, req *remotesapi.RebaseRequest) (*remotesapi.RebaseResponse, error) {
   255  	logger := getReqLogger("GRPC", "Rebase")
   256  	defer func() { logger("finished") }()
   257  
   258  	cs := rs.getStore(req.RepoId, "Rebase")
   259  
   260  	if cs == nil {
   261  		return nil, status.Error(codes.Internal, "Could not get chunkstore")
   262  	}
   263  
   264  	logger(fmt.Sprintf("found %s/%s", req.RepoId.Org, req.RepoId.RepoName))
   265  
   266  	err := cs.Rebase(ctx)
   267  
   268  	if err != nil {
   269  		logger(fmt.Sprintf("error occurred during processing of Rebace rpc of %s/%s details: %v", req.RepoId.Org, req.RepoId.RepoName, err))
   270  		return nil, status.Error(codes.Internal, "Failed to rebase")
   271  	}
   272  
   273  	return &remotesapi.RebaseResponse{}, nil
   274  }
   275  
   276  func (rs *RemoteChunkStore) Root(ctx context.Context, req *remotesapi.RootRequest) (*remotesapi.RootResponse, error) {
   277  	logger := getReqLogger("GRPC", "Root")
   278  	defer func() { logger("finished") }()
   279  
   280  	cs := rs.getStore(req.RepoId, "Root")
   281  
   282  	if cs == nil {
   283  		return nil, status.Error(codes.Internal, "Could not get chunkstore")
   284  	}
   285  
   286  	h, err := cs.Root(ctx)
   287  
   288  	if err != nil {
   289  		logger(fmt.Sprintf("error occurred during processing of Root rpc of %s/%s details: %v", req.RepoId.Org, req.RepoId.RepoName, err))
   290  		return nil, status.Error(codes.Internal, "Failed to get root")
   291  	}
   292  
   293  	return &remotesapi.RootResponse{RootHash: h[:]}, nil
   294  }
   295  
   296  func (rs *RemoteChunkStore) Commit(ctx context.Context, req *remotesapi.CommitRequest) (*remotesapi.CommitResponse, error) {
   297  	logger := getReqLogger("GRPC", "Commit")
   298  	defer func() { logger("finished") }()
   299  
   300  	cs := rs.getStore(req.RepoId, "Commit")
   301  
   302  	if cs == nil {
   303  		return nil, status.Error(codes.Internal, "Could not get chunkstore")
   304  	}
   305  
   306  	logger(fmt.Sprintf("found %s/%s", req.RepoId.Org, req.RepoId.RepoName))
   307  
   308  	//should validate
   309  	updates := make(map[hash.Hash]uint32)
   310  	for _, cti := range req.ChunkTableInfo {
   311  		updates[hash.New(cti.Hash)] = cti.ChunkCount
   312  	}
   313  
   314  	_, err := cs.UpdateManifest(ctx, updates)
   315  
   316  	if err != nil {
   317  		logger(fmt.Sprintf("error occurred updating the manifest: %s", err.Error()))
   318  		return nil, status.Error(codes.Internal, "manifest update error")
   319  	}
   320  
   321  	currHash := hash.New(req.Current)
   322  	lastHash := hash.New(req.Last)
   323  
   324  	var ok bool
   325  	ok, err = cs.Commit(ctx, currHash, lastHash)
   326  
   327  	if err != nil {
   328  		logger(fmt.Sprintf("error occurred during processing of Commit of %s/%s last %s curr: %s details: %v", req.RepoId.Org, req.RepoId.RepoName, lastHash.String(), currHash.String(), err))
   329  		return nil, status.Error(codes.Internal, "Failed to rebase")
   330  	}
   331  
   332  	logger(fmt.Sprintf("committed %s/%s moved from %s -> %s", req.RepoId.Org, req.RepoId.RepoName, currHash.String(), lastHash.String()))
   333  	return &remotesapi.CommitResponse{Success: ok}, nil
   334  }
   335  
   336  func (rs *RemoteChunkStore) GetRepoMetadata(ctx context.Context, req *remotesapi.GetRepoMetadataRequest) (*remotesapi.GetRepoMetadataResponse, error) {
   337  	logger := getReqLogger("GRPC", "GetRepoMetadata")
   338  	defer func() { logger("finished") }()
   339  
   340  	cs := rs.getOrCreateStore(req.RepoId, "GetRepoMetadata", req.ClientRepoFormat.NbfVersion)
   341  	if cs == nil {
   342  		return nil, status.Error(codes.Internal, "Could not get chunkstore")
   343  	}
   344  
   345  	_, tfs, _, err := cs.Sources(ctx)
   346  
   347  	if err != nil {
   348  		return nil, err
   349  	}
   350  
   351  	var size uint64
   352  	for _, tf := range tfs {
   353  		path := filepath.Join(req.RepoId.Org, req.RepoId.RepoName, tf.FileID())
   354  		info, err := os.Stat(path)
   355  
   356  		if err != nil {
   357  			return nil, err
   358  		}
   359  
   360  		size += uint64(info.Size())
   361  	}
   362  
   363  	return &remotesapi.GetRepoMetadataResponse{
   364  		NbfVersion:  cs.Version(),
   365  		NbsVersion:  req.ClientRepoFormat.NbsVersion,
   366  		StorageSize: size,
   367  	}, nil
   368  }
   369  
   370  func (rs *RemoteChunkStore) ListTableFiles(ctx context.Context, req *remotesapi.ListTableFilesRequest) (*remotesapi.ListTableFilesResponse, error) {
   371  	logger := getReqLogger("GRPC", "ListTableFiles")
   372  	defer func() { logger("finished") }()
   373  
   374  	cs := rs.getStore(req.RepoId, "ListTableFiles")
   375  
   376  	if cs == nil {
   377  		return nil, status.Error(codes.Internal, "Could not get chunkstore")
   378  	}
   379  
   380  	logger(fmt.Sprintf("found repo %s/%s", req.RepoId.Org, req.RepoId.RepoName))
   381  
   382  	root, tables, appendixTables, err := cs.Sources(ctx)
   383  
   384  	if err != nil {
   385  		return nil, status.Error(codes.Internal, "failed to get sources")
   386  	}
   387  
   388  	tableFileInfo, err := getTableFileInfo(rs, logger, tables, req)
   389  	if err != nil {
   390  		return nil, err
   391  	}
   392  
   393  	appendixTableFileInfo, err := getTableFileInfo(rs, logger, appendixTables, req)
   394  	if err != nil {
   395  		return nil, err
   396  	}
   397  
   398  	resp := &remotesapi.ListTableFilesResponse{
   399  		RootHash:              root[:],
   400  		TableFileInfo:         tableFileInfo,
   401  		AppendixTableFileInfo: appendixTableFileInfo,
   402  	}
   403  
   404  	return resp, nil
   405  }
   406  
   407  func getTableFileInfo(rs *RemoteChunkStore, logger func(string), tableList []nbs.TableFile, req *remotesapi.ListTableFilesRequest) ([]*remotesapi.TableFileInfo, error) {
   408  	appendixTableFileInfo := make([]*remotesapi.TableFileInfo, 0)
   409  	for _, t := range tableList {
   410  		url, err := rs.getDownloadUrl(logger, req.RepoId.Org, req.RepoId.RepoName, t.FileID())
   411  		if err != nil {
   412  			return nil, status.Error(codes.Internal, "failed to get download url for "+t.FileID())
   413  		}
   414  
   415  		appendixTableFileInfo = append(appendixTableFileInfo, &remotesapi.TableFileInfo{
   416  			FileId:    t.FileID(),
   417  			NumChunks: uint32(t.NumChunks()),
   418  			Url:       url,
   419  		})
   420  	}
   421  	return appendixTableFileInfo, nil
   422  }
   423  
   424  // AddTableFiles updates the remote manifest with new table files without modifying the root hash.
   425  func (rs *RemoteChunkStore) AddTableFiles(ctx context.Context, req *remotesapi.AddTableFilesRequest) (*remotesapi.AddTableFilesResponse, error) {
   426  	logger := getReqLogger("GRPC", "Commit")
   427  	defer func() { logger("finished") }()
   428  
   429  	cs := rs.getStore(req.RepoId, "Commit")
   430  
   431  	if cs == nil {
   432  		return nil, status.Error(codes.Internal, "Could not get chunkstore")
   433  	}
   434  
   435  	logger(fmt.Sprintf("found %s/%s", req.RepoId.Org, req.RepoId.RepoName))
   436  
   437  	// should validate
   438  	updates := make(map[hash.Hash]uint32)
   439  	for _, cti := range req.ChunkTableInfo {
   440  		updates[hash.New(cti.Hash)] = cti.ChunkCount
   441  	}
   442  
   443  	_, err := cs.UpdateManifest(ctx, updates)
   444  
   445  	if err != nil {
   446  		logger(fmt.Sprintf("error occurred updating the manifest: %s", err.Error()))
   447  		return nil, status.Error(codes.Internal, "manifest update error")
   448  	}
   449  
   450  	return &remotesapi.AddTableFilesResponse{Success: true}, nil
   451  }
   452  
   453  func (rs *RemoteChunkStore) getStore(repoId *remotesapi.RepoId, rpcName string) *nbs.NomsBlockStore {
   454  	return rs.getOrCreateStore(repoId, rpcName, types.Format_Default.VersionString())
   455  }
   456  
   457  func (rs *RemoteChunkStore) getOrCreateStore(repoId *remotesapi.RepoId, rpcName, nbfVerStr string) *nbs.NomsBlockStore {
   458  	org := repoId.Org
   459  	repoName := repoId.RepoName
   460  
   461  	cs, err := rs.csCache.Get(org, repoName, nbfVerStr)
   462  
   463  	if err != nil {
   464  		log.Printf("Failed to retrieve chunkstore for %s/%s\n", org, repoName)
   465  	}
   466  
   467  	return cs
   468  }
   469  
   470  var requestId int32
   471  
   472  func incReqId() int32 {
   473  	return atomic.AddInt32(&requestId, 1)
   474  }
   475  
   476  func getReqLogger(method, callName string) func(string) {
   477  	callId := fmt.Sprintf("%s(%05d)", method, incReqId())
   478  	log.Println(callId, "new request for:", callName)
   479  
   480  	return func(msg string) {
   481  		log.Println(callId, "-", msg)
   482  	}
   483  }