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 }