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  }