github.com/weaviate/weaviate@v1.24.6/usecases/replica/transport.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 replica
    13  
    14  import (
    15  	"context"
    16  	"fmt"
    17  
    18  	"github.com/go-openapi/strfmt"
    19  	"github.com/weaviate/weaviate/entities/additional"
    20  	"github.com/weaviate/weaviate/entities/search"
    21  	"github.com/weaviate/weaviate/entities/storobj"
    22  	"github.com/weaviate/weaviate/usecases/objects"
    23  )
    24  
    25  const (
    26  	// RequestKey is used to marshalling request IDs
    27  	RequestKey = "request_id"
    28  )
    29  
    30  // Client is used to read and write objects on replicas
    31  type Client interface {
    32  	rClient
    33  	wClient
    34  }
    35  
    36  // StatusCode is communicate the cause of failure during replication
    37  type StatusCode int
    38  
    39  const (
    40  	StatusOK            = 0
    41  	StatusClassNotFound = iota + 200
    42  	StatusShardNotFound
    43  	StatusNotFound
    44  	StatusAlreadyExisted
    45  	StatusNotReady
    46  	StatusConflict = iota + 300
    47  	StatusPreconditionFailed
    48  	StatusReadOnly
    49  )
    50  
    51  // Error reports error happening during replication
    52  type Error struct {
    53  	Code StatusCode `json:"code"`
    54  	Msg  string     `json:"msg,omitempty"`
    55  	Err  error      `json:"-"`
    56  }
    57  
    58  // Empty checks whether e is an empty error which equivalent to e == nil
    59  func (e *Error) Empty() bool {
    60  	return e.Code == StatusOK && e.Msg == "" && e.Err == nil
    61  }
    62  
    63  // NewError create new replication error
    64  func NewError(code StatusCode, msg string) *Error {
    65  	return &Error{code, msg, nil}
    66  }
    67  
    68  func (e *Error) Clone() *Error {
    69  	return &Error{Code: e.Code, Msg: e.Msg, Err: e.Err}
    70  }
    71  
    72  // Unwrap underlying error
    73  func (e *Error) Unwrap() error { return e.Err }
    74  
    75  func (e *Error) Error() string {
    76  	return fmt.Sprintf("%s %q: %v", statusText(e.Code), e.Msg, e.Err)
    77  }
    78  
    79  func (e *Error) IsStatusCode(sc StatusCode) bool {
    80  	return e.Code == sc
    81  }
    82  
    83  // statusText returns a text for the status code. It returns the empty
    84  // string if the code is unknown.
    85  func statusText(code StatusCode) string {
    86  	switch code {
    87  	case StatusOK:
    88  		return "ok"
    89  	case StatusNotFound:
    90  		return "not found"
    91  	case StatusClassNotFound:
    92  		return "class not found"
    93  	case StatusShardNotFound:
    94  		return "shard not found"
    95  	case StatusConflict:
    96  		return "conflict"
    97  	case StatusPreconditionFailed:
    98  		return "precondition failed"
    99  	case StatusAlreadyExisted:
   100  		return "already existed"
   101  	case StatusNotReady:
   102  		return "local index not ready"
   103  	case StatusReadOnly:
   104  		return "read only"
   105  	default:
   106  		return ""
   107  	}
   108  }
   109  
   110  func (e *Error) Timeout() bool {
   111  	t, ok := e.Err.(interface {
   112  		Timeout() bool
   113  	})
   114  	return ok && t.Timeout()
   115  }
   116  
   117  type SimpleResponse struct {
   118  	Errors []Error `json:"errors,omitempty"`
   119  }
   120  
   121  func (r *SimpleResponse) FirstError() error {
   122  	for i, err := range r.Errors {
   123  		if !err.Empty() {
   124  			return &r.Errors[i]
   125  		}
   126  	}
   127  	return nil
   128  }
   129  
   130  // DeleteBatchResponse represents the response returned by DeleteObjects
   131  type DeleteBatchResponse struct {
   132  	Batch []UUID2Error `json:"batch,omitempty"`
   133  }
   134  
   135  type UUID2Error struct {
   136  	UUID  string `json:"uuid,omitempty"`
   137  	Error Error  `json:"error,omitempty"`
   138  }
   139  
   140  // FirstError returns the first found error
   141  func (r *DeleteBatchResponse) FirstError() error {
   142  	for i, ue := range r.Batch {
   143  		if !ue.Error.Empty() {
   144  			return &r.Batch[i].Error
   145  		}
   146  	}
   147  	return nil
   148  }
   149  
   150  type RepairResponse struct {
   151  	ID         string // object id
   152  	Version    int64  // sender's current version of the object
   153  	UpdateTime int64  // sender's current update time
   154  	Err        string
   155  	Deleted    bool
   156  }
   157  
   158  func fromReplicas(xs []objects.Replica) []*storobj.Object {
   159  	rs := make([]*storobj.Object, len(xs))
   160  	for i := range xs {
   161  		rs[i] = xs[i].Object
   162  	}
   163  	return rs
   164  }
   165  
   166  // wClient is the client used to write to replicas
   167  type wClient interface {
   168  	PutObject(ctx context.Context, host, index, shard, requestID string,
   169  		obj *storobj.Object) (SimpleResponse, error)
   170  	DeleteObject(ctx context.Context, host, index, shard, requestID string,
   171  		id strfmt.UUID) (SimpleResponse, error)
   172  	PutObjects(ctx context.Context, host, index, shard, requestID string,
   173  		objs []*storobj.Object) (SimpleResponse, error)
   174  	MergeObject(ctx context.Context, host, index, shard, requestID string,
   175  		mergeDoc *objects.MergeDocument) (SimpleResponse, error)
   176  	DeleteObjects(ctx context.Context, host, index, shard, requestID string,
   177  		uuids []strfmt.UUID, dryRun bool) (SimpleResponse, error)
   178  	AddReferences(ctx context.Context, host, index, shard, requestID string,
   179  		refs []objects.BatchReference) (SimpleResponse, error)
   180  	Commit(ctx context.Context, host, index, shard, requestID string, resp interface{}) error
   181  	Abort(ctx context.Context, host, index, shard, requestID string) (SimpleResponse, error)
   182  }
   183  
   184  // rClient is the client used to read from remote replicas
   185  type rClient interface {
   186  	// FetchObject fetches one object
   187  	FetchObject(_ context.Context, host, index, shard string,
   188  		id strfmt.UUID, props search.SelectProperties,
   189  		additional additional.Properties) (objects.Replica, error)
   190  
   191  	// FetchObjects fetches objects specified in ids list.
   192  	FetchObjects(_ context.Context, host, index, shard string,
   193  		ids []strfmt.UUID) ([]objects.Replica, error)
   194  
   195  	// OverwriteObjects conditionally updates existing objects.
   196  	OverwriteObjects(_ context.Context, host, index, shard string,
   197  		_ []*objects.VObject) ([]RepairResponse, error)
   198  
   199  	// DigestObjects finds a list of objects and returns a compact representation
   200  	// of a list of the objects. This is used by the replicator to optimize the
   201  	// number of bytes transferred over the network when fetching a replicated
   202  	// object
   203  	DigestObjects(ctx context.Context, host, index, shard string,
   204  		ids []strfmt.UUID) ([]RepairResponse, error)
   205  }
   206  
   207  // finderClient extends RClient with consistency checks
   208  type finderClient struct {
   209  	cl rClient
   210  }
   211  
   212  // FullRead reads full object
   213  func (fc finderClient) FullRead(ctx context.Context,
   214  	host, index, shard string,
   215  	id strfmt.UUID,
   216  	props search.SelectProperties,
   217  	additional additional.Properties,
   218  ) (objects.Replica, error) {
   219  	return fc.cl.FetchObject(ctx, host, index, shard, id, props, additional)
   220  }
   221  
   222  // DigestReads reads digests of all specified objects
   223  func (fc finderClient) DigestReads(ctx context.Context,
   224  	host, index, shard string,
   225  	ids []strfmt.UUID,
   226  ) ([]RepairResponse, error) {
   227  	n := len(ids)
   228  	rs, err := fc.cl.DigestObjects(ctx, host, index, shard, ids)
   229  	if err == nil && len(rs) != n {
   230  		err = fmt.Errorf("malformed digest read response: length expected %d got %d", n, len(rs))
   231  	}
   232  	return rs, err
   233  }
   234  
   235  // FullReads read full objects
   236  func (fc finderClient) FullReads(ctx context.Context,
   237  	host, index, shard string,
   238  	ids []strfmt.UUID,
   239  ) ([]objects.Replica, error) {
   240  	n := len(ids)
   241  	rs, err := fc.cl.FetchObjects(ctx, host, index, shard, ids)
   242  	if m := len(rs); err == nil && n != m {
   243  		err = fmt.Errorf("malformed full read response: length expected %d got %d", n, m)
   244  	}
   245  	return rs, err
   246  }
   247  
   248  // Overwrite specified object with most recent contents
   249  func (fc finderClient) Overwrite(ctx context.Context,
   250  	host, index, shard string,
   251  	xs []*objects.VObject,
   252  ) ([]RepairResponse, error) {
   253  	return fc.cl.OverwriteObjects(ctx, host, index, shard, xs)
   254  }