github.com/weaviate/weaviate@v1.24.6/adapters/clients/remote_index.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 clients 13 14 import ( 15 "bytes" 16 "context" 17 "encoding/base64" 18 "encoding/json" 19 "fmt" 20 "io" 21 "net/http" 22 "net/url" 23 24 "github.com/go-openapi/strfmt" 25 "github.com/pkg/errors" 26 "github.com/weaviate/weaviate/adapters/handlers/rest/clusterapi" 27 "github.com/weaviate/weaviate/entities/additional" 28 "github.com/weaviate/weaviate/entities/aggregation" 29 "github.com/weaviate/weaviate/entities/filters" 30 "github.com/weaviate/weaviate/entities/search" 31 "github.com/weaviate/weaviate/entities/searchparams" 32 "github.com/weaviate/weaviate/entities/storobj" 33 "github.com/weaviate/weaviate/usecases/objects" 34 "github.com/weaviate/weaviate/usecases/scaler" 35 ) 36 37 type RemoteIndex struct { 38 retryClient 39 } 40 41 func NewRemoteIndex(httpClient *http.Client) *RemoteIndex { 42 return &RemoteIndex{retryClient: retryClient{ 43 client: httpClient, 44 retryer: newRetryer(), 45 }} 46 } 47 48 func (c *RemoteIndex) PutObject(ctx context.Context, hostName, indexName, 49 shardName string, obj *storobj.Object, 50 ) error { 51 path := fmt.Sprintf("/indices/%s/shards/%s/objects", indexName, shardName) 52 method := http.MethodPost 53 url := url.URL{Scheme: "http", Host: hostName, Path: path} 54 55 marshalled, err := clusterapi.IndicesPayloads.SingleObject.Marshal(obj) 56 if err != nil { 57 return errors.Wrap(err, "marshal payload") 58 } 59 60 req, err := http.NewRequestWithContext(ctx, method, url.String(), 61 bytes.NewReader(marshalled)) 62 if err != nil { 63 return errors.Wrap(err, "open http request") 64 } 65 66 clusterapi.IndicesPayloads.SingleObject.SetContentTypeHeaderReq(req) 67 res, err := c.client.Do(req) 68 if err != nil { 69 return errors.Wrap(err, "send http request") 70 } 71 72 defer res.Body.Close() 73 if res.StatusCode != http.StatusNoContent { 74 body, _ := io.ReadAll(res.Body) 75 return errors.Errorf("unexpected status code %d (%s)", res.StatusCode, 76 body) 77 } 78 79 return nil 80 } 81 82 func duplicateErr(in error, count int) []error { 83 out := make([]error, count) 84 for i := range out { 85 out[i] = in 86 } 87 return out 88 } 89 90 func (c *RemoteIndex) BatchPutObjects(ctx context.Context, hostName, indexName, 91 shardName string, objs []*storobj.Object, _ *additional.ReplicationProperties, 92 ) []error { 93 path := fmt.Sprintf("/indices/%s/shards/%s/objects", indexName, shardName) 94 method := http.MethodPost 95 url := url.URL{Scheme: "http", Host: hostName, Path: path} 96 97 marshalled, err := clusterapi.IndicesPayloads.ObjectList.Marshal(objs) 98 if err != nil { 99 return duplicateErr(errors.Wrap(err, "marshal payload"), len(objs)) 100 } 101 102 req, err := http.NewRequestWithContext(ctx, method, url.String(), 103 bytes.NewReader(marshalled)) 104 if err != nil { 105 return duplicateErr(errors.Wrap(err, "open http request"), len(objs)) 106 } 107 108 clusterapi.IndicesPayloads.ObjectList.SetContentTypeHeaderReq(req) 109 110 res, err := c.client.Do(req) 111 if err != nil { 112 return duplicateErr(errors.Wrap(err, "send http request"), len(objs)) 113 } 114 115 defer res.Body.Close() 116 if res.StatusCode != http.StatusOK { 117 body, _ := io.ReadAll(res.Body) 118 return duplicateErr(errors.Errorf("unexpected status code %d (%s)", 119 res.StatusCode, body), len(objs)) 120 } 121 122 if ct, ok := clusterapi.IndicesPayloads.ErrorList. 123 CheckContentTypeHeader(res); !ok { 124 return duplicateErr(errors.Errorf("unexpected content type: %s", 125 ct), len(objs)) 126 } 127 128 resBytes, err := io.ReadAll(res.Body) 129 if err != nil { 130 return duplicateErr(errors.Wrap(err, "ready body"), len(objs)) 131 } 132 133 return clusterapi.IndicesPayloads.ErrorList.Unmarshal(resBytes) 134 } 135 136 func (c *RemoteIndex) BatchAddReferences(ctx context.Context, hostName, indexName, 137 shardName string, refs objects.BatchReferences, 138 ) []error { 139 path := fmt.Sprintf("/indices/%s/shards/%s/references", indexName, shardName) 140 method := http.MethodPost 141 url := url.URL{Scheme: "http", Host: hostName, Path: path} 142 143 marshalled, err := clusterapi.IndicesPayloads.ReferenceList.Marshal(refs) 144 if err != nil { 145 return duplicateErr(errors.Wrap(err, "marshal payload"), len(refs)) 146 } 147 148 req, err := http.NewRequestWithContext(ctx, method, url.String(), 149 bytes.NewReader(marshalled)) 150 if err != nil { 151 return duplicateErr(errors.Wrap(err, "open http request"), len(refs)) 152 } 153 154 clusterapi.IndicesPayloads.ReferenceList.SetContentTypeHeaderReq(req) 155 156 res, err := c.client.Do(req) 157 if err != nil { 158 return duplicateErr(errors.Wrap(err, "send http request"), len(refs)) 159 } 160 161 defer res.Body.Close() 162 if res.StatusCode != http.StatusOK { 163 body, _ := io.ReadAll(res.Body) 164 return duplicateErr(errors.Errorf("unexpected status code %d (%s)", 165 res.StatusCode, body), len(refs)) 166 } 167 168 if ct, ok := clusterapi.IndicesPayloads.ErrorList. 169 CheckContentTypeHeader(res); !ok { 170 return duplicateErr(errors.Errorf("unexpected content type: %s", 171 ct), len(refs)) 172 } 173 174 resBytes, err := io.ReadAll(res.Body) 175 if err != nil { 176 return duplicateErr(errors.Wrap(err, "ready body"), len(refs)) 177 } 178 179 return clusterapi.IndicesPayloads.ErrorList.Unmarshal(resBytes) 180 } 181 182 func (c *RemoteIndex) GetObject(ctx context.Context, hostName, indexName, 183 shardName string, id strfmt.UUID, selectProps search.SelectProperties, 184 additional additional.Properties, 185 ) (*storobj.Object, error) { 186 selectPropsBytes, err := json.Marshal(selectProps) 187 if err != nil { 188 return nil, errors.Wrap(err, "marshal selectProps props") 189 } 190 191 additionalBytes, err := json.Marshal(additional) 192 if err != nil { 193 return nil, errors.Wrap(err, "marshal additional props") 194 } 195 196 selectPropsEncoded := base64.StdEncoding.EncodeToString(selectPropsBytes) 197 additionalEncoded := base64.StdEncoding.EncodeToString(additionalBytes) 198 199 path := fmt.Sprintf("/indices/%s/shards/%s/objects/%s", indexName, shardName, id) 200 method := http.MethodGet 201 url := url.URL{Scheme: "http", Host: hostName, Path: path} 202 q := url.Query() 203 q.Set("additional", additionalEncoded) 204 q.Set("selectProperties", selectPropsEncoded) 205 url.RawQuery = q.Encode() 206 207 req, err := http.NewRequestWithContext(ctx, method, url.String(), nil) 208 if err != nil { 209 return nil, errors.Wrap(err, "open http request") 210 } 211 212 res, err := c.client.Do(req) 213 if err != nil { 214 return nil, errors.Wrap(err, "send http request") 215 } 216 217 defer res.Body.Close() 218 if res.StatusCode == http.StatusNotFound { 219 // this is a legitimate case - the requested ID doesn't exist, don't try 220 // to unmarshal anything 221 return nil, nil 222 } 223 224 if res.StatusCode != http.StatusOK { 225 body, _ := io.ReadAll(res.Body) 226 return nil, errors.Errorf("unexpected status code %d (%s)", res.StatusCode, 227 body) 228 } 229 230 ct, ok := clusterapi.IndicesPayloads.SingleObject.CheckContentTypeHeader(res) 231 if !ok { 232 return nil, errors.Errorf("unknown content type %s", ct) 233 } 234 235 objBytes, err := io.ReadAll(res.Body) 236 if err != nil { 237 return nil, errors.Wrap(err, "read body") 238 } 239 240 obj, err := clusterapi.IndicesPayloads.SingleObject.Unmarshal(objBytes) 241 if err != nil { 242 return nil, errors.Wrap(err, "unmarshal body") 243 } 244 245 return obj, nil 246 } 247 248 func (c *RemoteIndex) Exists(ctx context.Context, hostName, indexName, 249 shardName string, id strfmt.UUID, 250 ) (bool, error) { 251 path := fmt.Sprintf("/indices/%s/shards/%s/objects/%s", indexName, shardName, id) 252 method := http.MethodGet 253 url := url.URL{Scheme: "http", Host: hostName, Path: path} 254 q := url.Query() 255 q.Set("check_exists", "true") 256 url.RawQuery = q.Encode() 257 258 req, err := http.NewRequestWithContext(ctx, method, url.String(), nil) 259 if err != nil { 260 return false, errors.Wrap(err, "open http request") 261 } 262 263 res, err := c.client.Do(req) 264 if err != nil { 265 return false, errors.Wrap(err, "send http request") 266 } 267 268 defer res.Body.Close() 269 if res.StatusCode == http.StatusNotFound { 270 // this is a legitimate case - the requested ID doesn't exist, don't try 271 // to unmarshal anything 272 return false, nil 273 } 274 275 if res.StatusCode != http.StatusNoContent { 276 body, _ := io.ReadAll(res.Body) 277 return false, errors.Errorf("unexpected status code %d (%s)", res.StatusCode, 278 body) 279 } 280 281 return true, nil 282 } 283 284 func (c *RemoteIndex) DeleteObject(ctx context.Context, hostName, indexName, 285 shardName string, id strfmt.UUID, 286 ) error { 287 path := fmt.Sprintf("/indices/%s/shards/%s/objects/%s", indexName, shardName, id) 288 method := http.MethodDelete 289 url := url.URL{Scheme: "http", Host: hostName, Path: path} 290 291 req, err := http.NewRequestWithContext(ctx, method, url.String(), nil) 292 if err != nil { 293 return errors.Wrap(err, "open http request") 294 } 295 296 res, err := c.client.Do(req) 297 if err != nil { 298 return errors.Wrap(err, "send http request") 299 } 300 301 defer res.Body.Close() 302 if res.StatusCode == http.StatusNotFound { 303 // this is a legitimate case - the requested ID doesn't exist, don't try 304 // to unmarshal anything, we can assume it was already deleted 305 return nil 306 } 307 308 if res.StatusCode != http.StatusNoContent { 309 body, _ := io.ReadAll(res.Body) 310 return errors.Errorf("unexpected status code %d (%s)", res.StatusCode, 311 body) 312 } 313 314 return nil 315 } 316 317 func (c *RemoteIndex) MergeObject(ctx context.Context, hostName, indexName, 318 shardName string, mergeDoc objects.MergeDocument, 319 ) error { 320 path := fmt.Sprintf("/indices/%s/shards/%s/objects/%s", indexName, shardName, 321 mergeDoc.ID) 322 method := http.MethodPatch 323 url := url.URL{Scheme: "http", Host: hostName, Path: path} 324 325 marshalled, err := clusterapi.IndicesPayloads.MergeDoc.Marshal(mergeDoc) 326 if err != nil { 327 return errors.Wrap(err, "marshal payload") 328 } 329 330 req, err := http.NewRequestWithContext(ctx, method, url.String(), 331 bytes.NewReader(marshalled)) 332 if err != nil { 333 return errors.Wrap(err, "open http request") 334 } 335 336 clusterapi.IndicesPayloads.MergeDoc.SetContentTypeHeaderReq(req) 337 res, err := c.client.Do(req) 338 if err != nil { 339 return errors.Wrap(err, "send http request") 340 } 341 342 defer res.Body.Close() 343 if res.StatusCode != http.StatusNoContent { 344 body, _ := io.ReadAll(res.Body) 345 return errors.Errorf("unexpected status code %d (%s)", res.StatusCode, 346 body) 347 } 348 349 return nil 350 } 351 352 func (c *RemoteIndex) MultiGetObjects(ctx context.Context, hostName, indexName, 353 shardName string, ids []strfmt.UUID, 354 ) ([]*storobj.Object, error) { 355 idsBytes, err := json.Marshal(ids) 356 if err != nil { 357 return nil, errors.Wrap(err, "marshal selectProps props") 358 } 359 360 idsEncoded := base64.StdEncoding.EncodeToString(idsBytes) 361 362 path := fmt.Sprintf("/indices/%s/shards/%s/objects", indexName, shardName) 363 method := http.MethodGet 364 url := url.URL{Scheme: "http", Host: hostName, Path: path} 365 q := url.Query() 366 q.Set("ids", idsEncoded) 367 url.RawQuery = q.Encode() 368 369 req, err := http.NewRequestWithContext(ctx, method, url.String(), nil) 370 if err != nil { 371 return nil, errors.Wrap(err, "open http request") 372 } 373 374 res, err := c.client.Do(req) 375 if err != nil { 376 return nil, errors.Wrap(err, "send http request") 377 } 378 379 defer res.Body.Close() 380 if res.StatusCode == http.StatusNotFound { 381 // this is a legitimate case - the requested ID doesn't exist, don't try 382 // to unmarshal anything 383 return nil, nil 384 } 385 386 if res.StatusCode != http.StatusOK { 387 body, _ := io.ReadAll(res.Body) 388 return nil, errors.Errorf("unexpected status code %d (%s)", res.StatusCode, 389 body) 390 } 391 392 ct, ok := clusterapi.IndicesPayloads.ObjectList.CheckContentTypeHeader(res) 393 if !ok { 394 return nil, errors.Errorf("unexpected content type: %s", ct) 395 } 396 397 bodyBytes, err := io.ReadAll(res.Body) 398 if err != nil { 399 return nil, errors.Wrap(err, "read response body") 400 } 401 402 objs, err := clusterapi.IndicesPayloads.ObjectList.Unmarshal(bodyBytes) 403 if err != nil { 404 return nil, errors.Wrap(err, "unmarshal objects") 405 } 406 407 return objs, nil 408 } 409 410 func (c *RemoteIndex) SearchShard(ctx context.Context, host, index, shard string, 411 vector []float32, 412 targetVector string, 413 limit int, 414 filters *filters.LocalFilter, 415 keywordRanking *searchparams.KeywordRanking, 416 sort []filters.Sort, 417 cursor *filters.Cursor, 418 groupBy *searchparams.GroupBy, 419 additional additional.Properties, 420 ) ([]*storobj.Object, []float32, error) { 421 // new request 422 body, err := clusterapi.IndicesPayloads.SearchParams. 423 Marshal(vector, targetVector, limit, filters, keywordRanking, sort, cursor, groupBy, additional) 424 if err != nil { 425 return nil, nil, fmt.Errorf("marshal request payload: %w", err) 426 } 427 url := url.URL{ 428 Scheme: "http", 429 Host: host, 430 Path: fmt.Sprintf("/indices/%s/shards/%s/objects/_search", index, shard), 431 } 432 req, err := http.NewRequestWithContext(ctx, http.MethodPost, url.String(), bytes.NewReader(body)) 433 if err != nil { 434 return nil, nil, fmt.Errorf("create http request: %w", err) 435 } 436 clusterapi.IndicesPayloads.SearchParams.SetContentTypeHeaderReq(req) 437 438 // send request 439 resp := &searchShardResp{} 440 err = c.doWithCustomMarshaller(c.timeoutUnit*20, req, body, resp.decode) 441 return resp.Objects, resp.Distributions, err 442 } 443 444 type searchShardResp struct { 445 Objects []*storobj.Object 446 Distributions []float32 447 } 448 449 func (r *searchShardResp) decode(data []byte) (err error) { 450 r.Objects, r.Distributions, err = clusterapi.IndicesPayloads.SearchResults.Unmarshal(data) 451 return 452 } 453 454 type aggregateResp struct { 455 Result *aggregation.Result 456 } 457 458 func (r *aggregateResp) decode(data []byte) (err error) { 459 r.Result, err = clusterapi.IndicesPayloads.AggregationResult.Unmarshal(data) 460 return 461 } 462 463 func (c *RemoteIndex) Aggregate(ctx context.Context, hostName, index, 464 shard string, params aggregation.Params, 465 ) (*aggregation.Result, error) { 466 // create new request 467 body, err := clusterapi.IndicesPayloads.AggregationParams.Marshal(params) 468 if err != nil { 469 return nil, fmt.Errorf("marshal request payload: %w", err) 470 } 471 472 url := &url.URL{ 473 Scheme: "http", 474 Host: hostName, 475 Path: fmt.Sprintf("/indices/%s/shards/%s/objects/_aggregations", index, shard), 476 } 477 req, err := http.NewRequestWithContext(ctx, http.MethodPost, url.String(), bytes.NewReader(body)) 478 if err != nil { 479 return nil, fmt.Errorf("create http request: %w", err) 480 } 481 clusterapi.IndicesPayloads.AggregationParams.SetContentTypeHeaderReq(req) 482 483 // send request 484 resp := &aggregateResp{} 485 err = c.doWithCustomMarshaller(c.timeoutUnit*20, req, body, resp.decode) 486 return resp.Result, err 487 } 488 489 func (c *RemoteIndex) FindUUIDs(ctx context.Context, hostName, indexName, 490 shardName string, filters *filters.LocalFilter, 491 ) ([]strfmt.UUID, error) { 492 paramsBytes, err := clusterapi.IndicesPayloads.FindUUIDsParams.Marshal(filters) 493 if err != nil { 494 return nil, errors.Wrap(err, "marshal request payload") 495 } 496 497 path := fmt.Sprintf("/indices/%s/shards/%s/objects/_find", indexName, shardName) 498 method := http.MethodPost 499 url := url.URL{Scheme: "http", Host: hostName, Path: path} 500 501 req, err := http.NewRequestWithContext(ctx, method, url.String(), 502 bytes.NewReader(paramsBytes)) 503 if err != nil { 504 return nil, errors.Wrap(err, "open http request") 505 } 506 507 clusterapi.IndicesPayloads.FindUUIDsParams.SetContentTypeHeaderReq(req) 508 res, err := c.client.Do(req) 509 if err != nil { 510 return nil, errors.Wrap(err, "send http request") 511 } 512 513 defer res.Body.Close() 514 if res.StatusCode != http.StatusOK { 515 body, _ := io.ReadAll(res.Body) 516 return nil, errors.Errorf("unexpected status code %d (%s)", res.StatusCode, 517 body) 518 } 519 520 resBytes, err := io.ReadAll(res.Body) 521 if err != nil { 522 return nil, errors.Wrap(err, "read body") 523 } 524 525 ct, ok := clusterapi.IndicesPayloads.FindUUIDsResults.CheckContentTypeHeader(res) 526 if !ok { 527 return nil, errors.Errorf("unexpected content type: %s", ct) 528 } 529 530 uuids, err := clusterapi.IndicesPayloads.FindUUIDsResults.Unmarshal(resBytes) 531 if err != nil { 532 return nil, errors.Wrap(err, "unmarshal body") 533 } 534 return uuids, nil 535 } 536 537 func (c *RemoteIndex) DeleteObjectBatch(ctx context.Context, hostName, indexName, shardName string, 538 uuids []strfmt.UUID, dryRun bool, 539 ) objects.BatchSimpleObjects { 540 path := fmt.Sprintf("/indices/%s/shards/%s/objects", indexName, shardName) 541 method := http.MethodDelete 542 url := url.URL{Scheme: "http", Host: hostName, Path: path} 543 544 marshalled, err := clusterapi.IndicesPayloads.BatchDeleteParams.Marshal(uuids, dryRun) 545 if err != nil { 546 err := errors.Wrap(err, "marshal payload") 547 return objects.BatchSimpleObjects{objects.BatchSimpleObject{Err: err}} 548 } 549 550 req, err := http.NewRequestWithContext(ctx, method, url.String(), 551 bytes.NewReader(marshalled)) 552 if err != nil { 553 err := errors.Wrap(err, "open http request") 554 return objects.BatchSimpleObjects{objects.BatchSimpleObject{Err: err}} 555 } 556 557 clusterapi.IndicesPayloads.BatchDeleteParams.SetContentTypeHeaderReq(req) 558 559 res, err := c.client.Do(req) 560 if err != nil { 561 err := errors.Wrap(err, "send http request") 562 return objects.BatchSimpleObjects{objects.BatchSimpleObject{Err: err}} 563 } 564 565 defer res.Body.Close() 566 if res.StatusCode != http.StatusOK { 567 body, _ := io.ReadAll(res.Body) 568 err := errors.Errorf("unexpected status code %d (%s)", res.StatusCode, body) 569 return objects.BatchSimpleObjects{objects.BatchSimpleObject{Err: err}} 570 } 571 572 if ct, ok := clusterapi.IndicesPayloads.BatchDeleteResults. 573 CheckContentTypeHeader(res); !ok { 574 err := errors.Errorf("unexpected content type: %s", ct) 575 return objects.BatchSimpleObjects{objects.BatchSimpleObject{Err: err}} 576 } 577 578 resBytes, err := io.ReadAll(res.Body) 579 if err != nil { 580 err := errors.Wrap(err, "ready body") 581 return objects.BatchSimpleObjects{objects.BatchSimpleObject{Err: err}} 582 } 583 584 batchDeleteResults, err := clusterapi.IndicesPayloads.BatchDeleteResults.Unmarshal(resBytes) 585 if err != nil { 586 err := errors.Wrap(err, "unmarshal body") 587 return objects.BatchSimpleObjects{objects.BatchSimpleObject{Err: err}} 588 } 589 590 return batchDeleteResults 591 } 592 593 func (c *RemoteIndex) GetShardQueueSize(ctx context.Context, 594 hostName, indexName, shardName string, 595 ) (int64, error) { 596 path := fmt.Sprintf("/indices/%s/shards/%s/queuesize", indexName, shardName) 597 method := http.MethodGet 598 url := url.URL{Scheme: "http", Host: hostName, Path: path} 599 600 req, err := http.NewRequestWithContext(ctx, method, url.String(), nil) 601 if err != nil { 602 return 0, errors.Wrap(err, "open http request") 603 } 604 var size int64 605 clusterapi.IndicesPayloads.GetShardQueueSizeParams.SetContentTypeHeaderReq(req) 606 try := func(ctx context.Context) (bool, error) { 607 res, err := c.client.Do(req) 608 if err != nil { 609 return ctx.Err() == nil, fmt.Errorf("connect: %w", err) 610 } 611 defer res.Body.Close() 612 613 if code := res.StatusCode; code != http.StatusOK { 614 body, _ := io.ReadAll(res.Body) 615 return shouldRetry(code), fmt.Errorf("status code: %v body: (%s)", code, body) 616 } 617 resBytes, err := io.ReadAll(res.Body) 618 if err != nil { 619 return false, errors.Wrap(err, "read body") 620 } 621 622 ct, ok := clusterapi.IndicesPayloads.GetShardQueueSizeResults.CheckContentTypeHeader(res) 623 if !ok { 624 return false, errors.Errorf("unexpected content type: %s", ct) 625 } 626 627 size, err = clusterapi.IndicesPayloads.GetShardQueueSizeResults.Unmarshal(resBytes) 628 if err != nil { 629 return false, errors.Wrap(err, "unmarshal body") 630 } 631 return false, nil 632 } 633 return size, c.retry(ctx, 9, try) 634 } 635 636 func (c *RemoteIndex) GetShardStatus(ctx context.Context, 637 hostName, indexName, shardName string, 638 ) (string, error) { 639 path := fmt.Sprintf("/indices/%s/shards/%s/status", indexName, shardName) 640 method := http.MethodGet 641 url := url.URL{Scheme: "http", Host: hostName, Path: path} 642 643 req, err := http.NewRequestWithContext(ctx, method, url.String(), nil) 644 if err != nil { 645 return "", errors.Wrap(err, "open http request") 646 } 647 var status string 648 clusterapi.IndicesPayloads.GetShardStatusParams.SetContentTypeHeaderReq(req) 649 try := func(ctx context.Context) (bool, error) { 650 res, err := c.client.Do(req) 651 if err != nil { 652 return ctx.Err() == nil, fmt.Errorf("connect: %w", err) 653 } 654 defer res.Body.Close() 655 656 if code := res.StatusCode; code != http.StatusOK { 657 body, _ := io.ReadAll(res.Body) 658 return shouldRetry(code), fmt.Errorf("status code: %v body: (%s)", code, body) 659 } 660 resBytes, err := io.ReadAll(res.Body) 661 if err != nil { 662 return false, errors.Wrap(err, "read body") 663 } 664 665 ct, ok := clusterapi.IndicesPayloads.GetShardStatusResults.CheckContentTypeHeader(res) 666 if !ok { 667 return false, errors.Errorf("unexpected content type: %s", ct) 668 } 669 670 status, err = clusterapi.IndicesPayloads.GetShardStatusResults.Unmarshal(resBytes) 671 if err != nil { 672 return false, errors.Wrap(err, "unmarshal body") 673 } 674 return false, nil 675 } 676 return status, c.retry(ctx, 9, try) 677 } 678 679 func (c *RemoteIndex) UpdateShardStatus(ctx context.Context, hostName, indexName, shardName, 680 targetStatus string, 681 ) error { 682 paramsBytes, err := clusterapi.IndicesPayloads.UpdateShardStatusParams.Marshal(targetStatus) 683 if err != nil { 684 return errors.Wrap(err, "marshal request payload") 685 } 686 path := fmt.Sprintf("/indices/%s/shards/%s/status", indexName, shardName) 687 method := http.MethodPost 688 url := url.URL{Scheme: "http", Host: hostName, Path: path} 689 690 try := func(ctx context.Context) (bool, error) { 691 req, err := http.NewRequestWithContext(ctx, method, url.String(), 692 bytes.NewReader(paramsBytes)) 693 if err != nil { 694 return false, fmt.Errorf("create http request: %w", err) 695 } 696 clusterapi.IndicesPayloads.UpdateShardStatusParams.SetContentTypeHeaderReq(req) 697 698 res, err := c.client.Do(req) 699 if err != nil { 700 return ctx.Err() == nil, fmt.Errorf("connect: %w", err) 701 } 702 defer res.Body.Close() 703 704 if code := res.StatusCode; code != http.StatusOK { 705 body, _ := io.ReadAll(res.Body) 706 return shouldRetry(code), fmt.Errorf("status code: %v body: (%s)", code, body) 707 } 708 709 return false, nil 710 } 711 712 return c.retry(ctx, 9, try) 713 } 714 715 func (c *RemoteIndex) PutFile(ctx context.Context, hostName, indexName, 716 shardName, fileName string, payload io.ReadSeekCloser, 717 ) error { 718 defer payload.Close() 719 path := fmt.Sprintf("/indices/%s/shards/%s/files/%s", 720 indexName, shardName, fileName) 721 722 method := http.MethodPost 723 url := url.URL{Scheme: "http", Host: hostName, Path: path} 724 try := func(ctx context.Context) (bool, error) { 725 req, err := http.NewRequestWithContext(ctx, method, url.String(), payload) 726 if err != nil { 727 return false, fmt.Errorf("create http request: %w", err) 728 } 729 clusterapi.IndicesPayloads.ShardFiles.SetContentTypeHeaderReq(req) 730 res, err := c.client.Do(req) 731 if err != nil { 732 return ctx.Err() == nil, fmt.Errorf("connect: %w", err) 733 } 734 defer res.Body.Close() 735 736 if code := res.StatusCode; code != http.StatusNoContent { 737 shouldRetry := shouldRetry(code) 738 if shouldRetry { 739 _, err := payload.Seek(0, 0) 740 shouldRetry = (err == nil) 741 } 742 body, _ := io.ReadAll(res.Body) 743 return shouldRetry, fmt.Errorf("status code: %v body: (%s)", code, body) 744 } 745 return false, nil 746 } 747 748 return c.retry(ctx, 12, try) 749 } 750 751 func (c *RemoteIndex) CreateShard(ctx context.Context, 752 hostName, indexName, shardName string, 753 ) error { 754 path := fmt.Sprintf("/indices/%s/shards/%s", indexName, shardName) 755 756 method := http.MethodPost 757 url := url.URL{Scheme: "http", Host: hostName, Path: path} 758 759 req, err := http.NewRequestWithContext(ctx, method, url.String(), nil) 760 if err != nil { 761 return fmt.Errorf("create http request: %w", err) 762 } 763 try := func(ctx context.Context) (bool, error) { 764 res, err := c.client.Do(req) 765 if err != nil { 766 return ctx.Err() == nil, fmt.Errorf("connect: %w", err) 767 } 768 defer res.Body.Close() 769 770 if code := res.StatusCode; code != http.StatusCreated { 771 body, _ := io.ReadAll(res.Body) 772 return shouldRetry(code), fmt.Errorf("status code: %v body: (%s)", code, body) 773 } 774 return false, nil 775 } 776 777 return c.retry(ctx, 9, try) 778 } 779 780 func (c *RemoteIndex) ReInitShard(ctx context.Context, 781 hostName, indexName, shardName string, 782 ) error { 783 path := fmt.Sprintf("/indices/%s/shards/%s:reinit", indexName, shardName) 784 785 method := http.MethodPut 786 url := url.URL{Scheme: "http", Host: hostName, Path: path} 787 788 req, err := http.NewRequestWithContext(ctx, method, url.String(), nil) 789 if err != nil { 790 return fmt.Errorf("create http request: %w", err) 791 } 792 try := func(ctx context.Context) (bool, error) { 793 res, err := c.client.Do(req) 794 if err != nil { 795 return ctx.Err() == nil, fmt.Errorf("connect: %w", err) 796 } 797 defer res.Body.Close() 798 799 if code := res.StatusCode; code != http.StatusNoContent { 800 body, _ := io.ReadAll(res.Body) 801 return shouldRetry(code), fmt.Errorf("status code: %v body: (%s)", code, body) 802 803 } 804 return false, nil 805 } 806 807 return c.retry(ctx, 9, try) 808 } 809 810 func (c *RemoteIndex) IncreaseReplicationFactor(ctx context.Context, 811 hostName, indexName string, dist scaler.ShardDist, 812 ) error { 813 path := fmt.Sprintf("/replicas/indices/%s/replication-factor:increase", indexName) 814 815 method := http.MethodPut 816 url := url.URL{Scheme: "http", Host: hostName, Path: path} 817 818 body, err := clusterapi.IndicesPayloads.IncreaseReplicationFactor.Marshall(dist) 819 if err != nil { 820 return err 821 } 822 try := func(ctx context.Context) (bool, error) { 823 req, err := http.NewRequestWithContext(ctx, method, url.String(), bytes.NewReader(body)) 824 if err != nil { 825 return false, fmt.Errorf("create http request: %w", err) 826 } 827 828 res, err := c.client.Do(req) 829 if err != nil { 830 return ctx.Err() == nil, fmt.Errorf("connect: %w", err) 831 } 832 defer res.Body.Close() 833 834 if code := res.StatusCode; code != http.StatusNoContent { 835 body, _ := io.ReadAll(res.Body) 836 return shouldRetry(code), fmt.Errorf("status code: %v body: (%s)", code, body) 837 } 838 return false, nil 839 } 840 return c.retry(ctx, 34, try) 841 }