github.com/weaviate/weaviate@v1.24.6/adapters/handlers/rest/clusterapi/indices_replicas.go (about) 1 // _ _ 2 // __ _____ __ ___ ___ __ _| |_ ___ 3 // \ \ /\ / / _ \/ _` \ \ / / |/ _` | __/ _ \ 4 // \ V V / __/ (_| |\ V /| | (_| | || __/ 5 // \_/\_/ \___|\__,_| \_/ |_|\__,_|\__\___| 6 // 7 // Copyright © 2016 - 2024 Weaviate B.V. All rights reserved. 8 // 9 // CONTACT: hello@weaviate.io 10 // 11 12 package clusterapi 13 14 import ( 15 "context" 16 "encoding/base64" 17 "encoding/json" 18 "fmt" 19 "io" 20 "net/http" 21 "regexp" 22 23 "github.com/go-openapi/strfmt" 24 "github.com/weaviate/weaviate/entities/storobj" 25 "github.com/weaviate/weaviate/usecases/objects" 26 "github.com/weaviate/weaviate/usecases/replica" 27 "github.com/weaviate/weaviate/usecases/scaler" 28 ) 29 30 type replicator interface { 31 // Write endpoints 32 ReplicateObject(ctx context.Context, indexName, shardName, 33 requestID string, object *storobj.Object) replica.SimpleResponse 34 ReplicateObjects(ctx context.Context, indexName, shardName, 35 requestID string, objects []*storobj.Object) replica.SimpleResponse 36 ReplicateUpdate(ctx context.Context, indexName, shardName, 37 requestID string, mergeDoc *objects.MergeDocument) replica.SimpleResponse 38 ReplicateDeletion(ctx context.Context, indexName, shardName, 39 requestID string, uuid strfmt.UUID) replica.SimpleResponse 40 ReplicateDeletions(ctx context.Context, indexName, shardName, 41 requestID string, uuids []strfmt.UUID, dryRun bool) replica.SimpleResponse 42 ReplicateReferences(ctx context.Context, indexName, shardName, 43 requestID string, refs []objects.BatchReference) replica.SimpleResponse 44 CommitReplication(indexName, 45 shardName, requestID string) interface{} 46 AbortReplication(indexName, 47 shardName, requestID string) interface{} 48 OverwriteObjects(ctx context.Context, index, shard string, 49 vobjects []*objects.VObject) ([]replica.RepairResponse, error) 50 // Read endpoints 51 FetchObject(ctx context.Context, indexName, 52 shardName string, id strfmt.UUID) (objects.Replica, error) 53 FetchObjects(ctx context.Context, class, 54 shardName string, ids []strfmt.UUID) ([]objects.Replica, error) 55 DigestObjects(ctx context.Context, class, shardName string, 56 ids []strfmt.UUID) (result []replica.RepairResponse, err error) 57 } 58 59 type localScaler interface { 60 LocalScaleOut(ctx context.Context, className string, 61 dist scaler.ShardDist) error 62 } 63 64 type replicatedIndices struct { 65 shards replicator 66 scaler localScaler 67 auth auth 68 } 69 70 var ( 71 regxObject = regexp.MustCompile(`\/replicas\/indices\/(` + cl + `)` + 72 `\/shards\/(` + sh + `)\/objects\/(` + ob + `)`) 73 regxOverwriteObjects = regexp.MustCompile(`\/indices\/(` + cl + `)` + 74 `\/shards\/(` + sh + `)\/objects/_overwrite`) 75 regxObjectsDigest = regexp.MustCompile(`\/indices\/(` + cl + `)` + 76 `\/shards\/(` + sh + `)\/objects/_digest`) 77 regxObjects = regexp.MustCompile(`\/replicas\/indices\/(` + cl + `)` + 78 `\/shards\/(` + sh + `)\/objects`) 79 regxReferences = regexp.MustCompile(`\/replicas\/indices\/(` + cl + `)` + 80 `\/shards\/(` + sh + `)\/objects/references`) 81 regxIncreaseRepFactor = regexp.MustCompile(`\/replicas\/indices\/(` + cl + `)` + 82 `\/replication-factor:increase`) 83 regxCommitPhase = regexp.MustCompile(`\/replicas\/indices\/(` + cl + `)` + 84 `\/shards\/(` + sh + `):(commit|abort)`) 85 ) 86 87 func NewReplicatedIndices(shards replicator, scaler localScaler, auth auth) *replicatedIndices { 88 return &replicatedIndices{ 89 shards: shards, 90 scaler: scaler, 91 auth: auth, 92 } 93 } 94 95 func (i *replicatedIndices) Indices() http.Handler { 96 return i.auth.handleFunc(i.indicesHandler()) 97 } 98 99 func (i *replicatedIndices) indicesHandler() http.HandlerFunc { 100 return func(w http.ResponseWriter, r *http.Request) { 101 path := r.URL.Path 102 switch { 103 case regxObjectsDigest.MatchString(path): 104 if r.Method == http.MethodGet { 105 i.getObjectsDigest().ServeHTTP(w, r) 106 return 107 } 108 109 http.Error(w, "405 Method not Allowed", http.StatusMethodNotAllowed) 110 return 111 case regxOverwriteObjects.MatchString(path): 112 if r.Method == http.MethodPut { 113 i.putOverwriteObjects().ServeHTTP(w, r) 114 return 115 } 116 117 http.Error(w, "405 Method not Allowed", http.StatusMethodNotAllowed) 118 return 119 case regxObject.MatchString(path): 120 if r.Method == http.MethodDelete { 121 i.deleteObject().ServeHTTP(w, r) 122 return 123 } 124 125 if r.Method == http.MethodPatch { 126 i.patchObject().ServeHTTP(w, r) 127 return 128 } 129 130 if r.Method == http.MethodGet { 131 i.getObject().ServeHTTP(w, r) 132 return 133 } 134 135 if regxReferences.MatchString(path) { 136 if r.Method == http.MethodPost { 137 i.postRefs().ServeHTTP(w, r) 138 return 139 } 140 } 141 142 http.Error(w, "405 Method not Allowed", http.StatusMethodNotAllowed) 143 return 144 145 case regxObjects.MatchString(path): 146 if r.Method == http.MethodGet { 147 i.getObjectsMulti().ServeHTTP(w, r) 148 return 149 } 150 151 if r.Method == http.MethodPost { 152 i.postObject().ServeHTTP(w, r) 153 return 154 } 155 156 if r.Method == http.MethodDelete { 157 i.deleteObjects().ServeHTTP(w, r) 158 return 159 } 160 161 http.Error(w, "405 Method not Allowed", http.StatusMethodNotAllowed) 162 return 163 164 case regxIncreaseRepFactor.MatchString(path): 165 if r.Method == http.MethodPut { 166 i.increaseReplicationFactor().ServeHTTP(w, r) 167 return 168 } 169 170 http.Error(w, "405 Method not Allowed", http.StatusMethodNotAllowed) 171 return 172 173 case regxCommitPhase.MatchString(path): 174 if r.Method == http.MethodPost { 175 i.executeCommitPhase().ServeHTTP(w, r) 176 return 177 } 178 179 http.Error(w, "405 Method not Allowed", http.StatusMethodNotAllowed) 180 return 181 182 default: 183 http.NotFound(w, r) 184 return 185 } 186 } 187 } 188 189 func (i *replicatedIndices) executeCommitPhase() http.Handler { 190 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 191 args := regxCommitPhase.FindStringSubmatch(r.URL.Path) 192 if len(args) != 4 { 193 http.Error(w, "invalid URI", http.StatusBadRequest) 194 return 195 } 196 197 requestID := r.URL.Query().Get(replica.RequestKey) 198 if requestID == "" { 199 http.Error(w, "request_id not provided", http.StatusBadRequest) 200 return 201 } 202 203 index, shard, cmd := args[1], args[2], args[3] 204 205 var resp interface{} 206 207 switch cmd { 208 case "commit": 209 resp = i.shards.CommitReplication(index, shard, requestID) 210 case "abort": 211 resp = i.shards.AbortReplication(index, shard, requestID) 212 default: 213 http.Error(w, fmt.Sprintf("unrecognized command: %s", cmd), http.StatusNotImplemented) 214 return 215 } 216 if resp == nil { // could not find request with specified id 217 http.Error(w, "request not found", http.StatusNotFound) 218 return 219 } 220 b, err := json.Marshal(resp) 221 if err != nil { 222 http.Error(w, fmt.Sprintf("failed to marshal response: %+v, error: %v", resp, err), 223 http.StatusInternalServerError) 224 return 225 } 226 w.Write(b) 227 }) 228 } 229 230 func (i *replicatedIndices) increaseReplicationFactor() http.Handler { 231 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 232 args := regxIncreaseRepFactor.FindStringSubmatch(r.URL.Path) 233 fmt.Printf("path: %v, args: %+v", r.URL.Path, args) 234 if len(args) != 2 { 235 http.Error(w, "invalid URI", http.StatusBadRequest) 236 return 237 } 238 239 index := args[1] 240 241 bodyBytes, err := io.ReadAll(r.Body) 242 if err != nil { 243 http.Error(w, err.Error(), http.StatusInternalServerError) 244 return 245 } 246 247 dist, err := IndicesPayloads.IncreaseReplicationFactor.Unmarshal(bodyBytes) 248 if err != nil { 249 http.Error(w, err.Error(), http.StatusInternalServerError) 250 return 251 } 252 253 if err := i.scaler.LocalScaleOut(r.Context(), index, dist); err != nil { 254 http.Error(w, err.Error(), http.StatusInternalServerError) 255 return 256 } 257 258 w.WriteHeader(http.StatusNoContent) 259 }) 260 } 261 262 func (i *replicatedIndices) postObject() http.Handler { 263 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 264 args := regxObjects.FindStringSubmatch(r.URL.Path) 265 if len(args) != 3 { 266 http.Error(w, "invalid URI", http.StatusBadRequest) 267 return 268 } 269 270 requestID := r.URL.Query().Get(replica.RequestKey) 271 if requestID == "" { 272 http.Error(w, "request_id not provided", http.StatusBadRequest) 273 return 274 } 275 276 index, shard := args[1], args[2] 277 278 defer r.Body.Close() 279 280 ct := r.Header.Get("content-type") 281 282 switch ct { 283 284 case IndicesPayloads.SingleObject.MIME(): 285 i.postObjectSingle(w, r, index, shard, requestID) 286 return 287 case IndicesPayloads.ObjectList.MIME(): 288 i.postObjectBatch(w, r, index, shard, requestID) 289 return 290 default: 291 http.Error(w, "415 Unsupported Media Type", http.StatusUnsupportedMediaType) 292 return 293 } 294 }) 295 } 296 297 func (i *replicatedIndices) patchObject() http.Handler { 298 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 299 args := regxObjects.FindStringSubmatch(r.URL.Path) 300 if len(args) != 3 { 301 http.Error(w, "invalid URI", http.StatusBadRequest) 302 return 303 } 304 305 requestID := r.URL.Query().Get(replica.RequestKey) 306 if requestID == "" { 307 http.Error(w, "request_id not provided", http.StatusBadRequest) 308 return 309 } 310 311 index, shard := args[1], args[2] 312 313 bodyBytes, err := io.ReadAll(r.Body) 314 if err != nil { 315 http.Error(w, err.Error(), http.StatusInternalServerError) 316 return 317 } 318 319 mergeDoc, err := IndicesPayloads.MergeDoc.Unmarshal(bodyBytes) 320 if err != nil { 321 http.Error(w, err.Error(), http.StatusInternalServerError) 322 return 323 } 324 325 resp := i.shards.ReplicateUpdate(r.Context(), index, shard, requestID, &mergeDoc) 326 if localIndexNotReady(resp) { 327 http.Error(w, resp.FirstError().Error(), http.StatusServiceUnavailable) 328 return 329 } 330 331 b, err := json.Marshal(resp) 332 if err != nil { 333 http.Error(w, fmt.Sprintf("failed to marshal response: %+v, error: %v", resp, err), 334 http.StatusInternalServerError) 335 return 336 } 337 338 w.Write(b) 339 }) 340 } 341 342 func (i *replicatedIndices) getObjectsDigest() http.Handler { 343 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 344 args := regxObjectsDigest.FindStringSubmatch(r.URL.Path) 345 if len(args) != 3 { 346 http.Error(w, "invalid URI", http.StatusBadRequest) 347 return 348 } 349 350 index, shard := args[1], args[2] 351 352 defer r.Body.Close() 353 reqPayload, err := io.ReadAll(r.Body) 354 if err != nil { 355 http.Error(w, "read request body: "+err.Error(), http.StatusInternalServerError) 356 return 357 } 358 359 var ids []strfmt.UUID 360 if err := json.Unmarshal(reqPayload, &ids); err != nil { 361 http.Error(w, "unmarshal digest objects params from json: "+err.Error(), 362 http.StatusBadRequest) 363 return 364 } 365 366 results, err := i.shards.DigestObjects(r.Context(), index, shard, ids) 367 if err != nil { 368 http.Error(w, "digest objects: "+err.Error(), 369 http.StatusInternalServerError) 370 return 371 } 372 373 resBytes, err := json.Marshal(results) 374 if err != nil { 375 http.Error(w, err.Error(), http.StatusInternalServerError) 376 return 377 } 378 379 w.Write(resBytes) 380 }) 381 } 382 383 func (i *replicatedIndices) putOverwriteObjects() http.Handler { 384 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 385 args := regxOverwriteObjects.FindStringSubmatch(r.URL.Path) 386 if len(args) != 3 { 387 http.Error(w, "invalid URI", http.StatusBadRequest) 388 return 389 } 390 391 index, shard := args[1], args[2] 392 393 defer r.Body.Close() 394 reqPayload, err := io.ReadAll(r.Body) 395 if err != nil { 396 http.Error(w, "read request body: "+err.Error(), http.StatusInternalServerError) 397 return 398 } 399 400 vobjs, err := IndicesPayloads.VersionedObjectList.Unmarshal(reqPayload) 401 if err != nil { 402 http.Error(w, "unmarshal overwrite objects params from json: "+err.Error(), 403 http.StatusBadRequest) 404 return 405 } 406 407 results, err := i.shards.OverwriteObjects(r.Context(), index, shard, vobjs) 408 if err != nil { 409 http.Error(w, "overwrite objects: "+err.Error(), 410 http.StatusInternalServerError) 411 return 412 } 413 414 resBytes, err := json.Marshal(results) 415 if err != nil { 416 http.Error(w, err.Error(), http.StatusInternalServerError) 417 return 418 } 419 420 w.Write(resBytes) 421 }) 422 } 423 424 func (i *replicatedIndices) deleteObject() http.Handler { 425 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 426 args := regxObject.FindStringSubmatch(r.URL.Path) 427 if len(args) != 4 { 428 http.Error(w, "invalid URI", http.StatusBadRequest) 429 return 430 } 431 432 requestID := r.URL.Query().Get(replica.RequestKey) 433 if requestID == "" { 434 http.Error(w, "request_id not provided", http.StatusBadRequest) 435 return 436 } 437 438 index, shard, id := args[1], args[2], args[3] 439 440 defer r.Body.Close() 441 442 resp := i.shards.ReplicateDeletion(r.Context(), index, shard, requestID, strfmt.UUID(id)) 443 if localIndexNotReady(resp) { 444 http.Error(w, resp.FirstError().Error(), http.StatusServiceUnavailable) 445 return 446 } 447 448 b, err := json.Marshal(resp) 449 if err != nil { 450 http.Error(w, fmt.Sprintf("failed to marshal response: %+v, error: %v", resp, err), 451 http.StatusInternalServerError) 452 return 453 } 454 w.Write(b) 455 }) 456 } 457 458 func (i *replicatedIndices) deleteObjects() http.Handler { 459 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 460 args := regxObjects.FindStringSubmatch(r.URL.Path) 461 if len(args) != 3 { 462 http.Error(w, "invalid URI", http.StatusBadRequest) 463 return 464 } 465 466 requestID := r.URL.Query().Get(replica.RequestKey) 467 if requestID == "" { 468 http.Error(w, "request_id not provided", http.StatusBadRequest) 469 return 470 } 471 472 index, shard := args[1], args[2] 473 474 bodyBytes, err := io.ReadAll(r.Body) 475 if err != nil { 476 http.Error(w, err.Error(), http.StatusInternalServerError) 477 return 478 } 479 defer r.Body.Close() 480 481 uuids, dryRun, err := IndicesPayloads.BatchDeleteParams.Unmarshal(bodyBytes) 482 if err != nil { 483 http.Error(w, err.Error(), http.StatusInternalServerError) 484 return 485 } 486 487 resp := i.shards.ReplicateDeletions(r.Context(), index, shard, requestID, uuids, dryRun) 488 if localIndexNotReady(resp) { 489 http.Error(w, resp.FirstError().Error(), http.StatusServiceUnavailable) 490 return 491 } 492 493 b, err := json.Marshal(resp) 494 if err != nil { 495 http.Error(w, fmt.Sprintf("failed to marshal response: %+v, error: %v", resp, err), 496 http.StatusInternalServerError) 497 return 498 } 499 w.Write(b) 500 }) 501 } 502 503 func (i *replicatedIndices) postObjectSingle(w http.ResponseWriter, r *http.Request, 504 index, shard, requestID string, 505 ) { 506 bodyBytes, err := io.ReadAll(r.Body) 507 if err != nil { 508 http.Error(w, err.Error(), http.StatusInternalServerError) 509 return 510 } 511 512 obj, err := IndicesPayloads.SingleObject.Unmarshal(bodyBytes) 513 if err != nil { 514 http.Error(w, err.Error(), http.StatusInternalServerError) 515 return 516 } 517 518 resp := i.shards.ReplicateObject(r.Context(), index, shard, requestID, obj) 519 if localIndexNotReady(resp) { 520 http.Error(w, resp.FirstError().Error(), http.StatusServiceUnavailable) 521 return 522 } 523 524 b, err := json.Marshal(resp) 525 if err != nil { 526 http.Error(w, fmt.Sprintf("failed to marshal response: %+v, error: %v", resp, err), 527 http.StatusInternalServerError) 528 return 529 } 530 531 w.Write(b) 532 } 533 534 func (i *replicatedIndices) postObjectBatch(w http.ResponseWriter, r *http.Request, 535 index, shard, requestID string, 536 ) { 537 bodyBytes, err := io.ReadAll(r.Body) 538 if err != nil { 539 http.Error(w, err.Error(), http.StatusInternalServerError) 540 return 541 } 542 543 objs, err := IndicesPayloads.ObjectList.Unmarshal(bodyBytes) 544 if err != nil { 545 http.Error(w, err.Error(), http.StatusInternalServerError) 546 return 547 } 548 549 resp := i.shards.ReplicateObjects(r.Context(), index, shard, requestID, objs) 550 if localIndexNotReady(resp) { 551 http.Error(w, resp.FirstError().Error(), http.StatusServiceUnavailable) 552 return 553 } 554 555 b, err := json.Marshal(resp) 556 if err != nil { 557 http.Error(w, fmt.Sprintf("unmarshal resp: %+v, error: %v", resp, err), 558 http.StatusInternalServerError) 559 return 560 } 561 562 w.Write(b) 563 } 564 565 func (i *replicatedIndices) getObject() http.Handler { 566 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 567 args := regxObject.FindStringSubmatch(r.URL.Path) 568 if len(args) != 4 { 569 http.Error(w, "invalid URI", http.StatusBadRequest) 570 return 571 } 572 573 index, shard, id := args[1], args[2], args[3] 574 575 defer r.Body.Close() 576 577 var ( 578 resp objects.Replica 579 err error 580 ) 581 582 resp, err = i.shards.FetchObject(r.Context(), index, shard, strfmt.UUID(id)) 583 if err != nil { 584 http.Error(w, err.Error(), http.StatusInternalServerError) 585 return 586 } 587 588 b, err := resp.MarshalBinary() 589 if err != nil { 590 http.Error(w, fmt.Sprintf("unmarshal resp: %+v, error: %v", resp, err), 591 http.StatusInternalServerError) 592 return 593 } 594 595 w.Write(b) 596 }) 597 } 598 599 func (i *replicatedIndices) getObjectsMulti() http.Handler { 600 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 601 args := regxObjects.FindStringSubmatch(r.URL.Path) 602 if len(args) != 3 { 603 http.Error(w, fmt.Sprintf("invalid URI: %s", r.URL.Path), 604 http.StatusBadRequest) 605 return 606 } 607 608 index, shard := args[1], args[2] 609 610 defer r.Body.Close() 611 612 idsEncoded := r.URL.Query().Get("ids") 613 if idsEncoded == "" { 614 http.Error(w, "missing required url param 'ids'", 615 http.StatusBadRequest) 616 return 617 } 618 619 idsBytes, err := base64.StdEncoding.DecodeString(idsEncoded) 620 if err != nil { 621 http.Error(w, "base64 decode 'ids' param: "+err.Error(), 622 http.StatusBadRequest) 623 return 624 } 625 626 var ids []strfmt.UUID 627 if err := json.Unmarshal(idsBytes, &ids); err != nil { 628 http.Error(w, "unmarshal 'ids' param from json: "+err.Error(), 629 http.StatusBadRequest) 630 return 631 } 632 633 resp, err := i.shards.FetchObjects(r.Context(), index, shard, ids) 634 if err != nil { 635 http.Error(w, err.Error(), http.StatusInternalServerError) 636 } 637 638 b, err := objects.Replicas(resp).MarshalBinary() 639 if err != nil { 640 http.Error(w, fmt.Sprintf("unmarshal resp: %+v, error: %v", resp, err), 641 http.StatusInternalServerError) 642 return 643 } 644 645 w.Write(b) 646 }) 647 } 648 649 func (i *replicatedIndices) postRefs() http.Handler { 650 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 651 args := regxObjects.FindStringSubmatch(r.URL.Path) 652 if len(args) != 3 { 653 http.Error(w, "invalid URI", http.StatusBadRequest) 654 return 655 } 656 657 requestID := r.URL.Query().Get(replica.RequestKey) 658 if requestID == "" { 659 http.Error(w, "request_id not provided", http.StatusBadRequest) 660 return 661 } 662 663 index, shard := args[1], args[2] 664 bodyBytes, err := io.ReadAll(r.Body) 665 if err != nil { 666 http.Error(w, err.Error(), http.StatusInternalServerError) 667 return 668 } 669 670 refs, err := IndicesPayloads.ReferenceList.Unmarshal(bodyBytes) 671 if err != nil { 672 http.Error(w, err.Error(), http.StatusInternalServerError) 673 return 674 } 675 676 resp := i.shards.ReplicateReferences(r.Context(), index, shard, requestID, refs) 677 if localIndexNotReady(resp) { 678 http.Error(w, resp.FirstError().Error(), http.StatusServiceUnavailable) 679 return 680 } 681 682 b, err := json.Marshal(resp) 683 if err != nil { 684 http.Error(w, fmt.Sprintf("unmarshal resp: %+v, error: %v", resp, err), 685 http.StatusInternalServerError) 686 return 687 } 688 689 w.Write(b) 690 }) 691 } 692 693 func localIndexNotReady(resp replica.SimpleResponse) bool { 694 if err := resp.FirstError(); err != nil { 695 re, ok := err.(*replica.Error) 696 if ok && re.IsStatusCode(replica.StatusNotReady) { 697 return true 698 } 699 } 700 return false 701 }