github.com/weaviate/weaviate@v1.24.6/adapters/clients/replication_test.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 "context" 16 "encoding/json" 17 "net/http" 18 "net/http/httptest" 19 "testing" 20 "time" 21 22 "github.com/go-openapi/strfmt" 23 "github.com/stretchr/testify/assert" 24 "github.com/stretchr/testify/require" 25 "github.com/weaviate/weaviate/entities/additional" 26 "github.com/weaviate/weaviate/entities/models" 27 "github.com/weaviate/weaviate/entities/storobj" 28 "github.com/weaviate/weaviate/usecases/objects" 29 "github.com/weaviate/weaviate/usecases/replica" 30 ) 31 32 const ( 33 RequestError = "RIDNotFound" 34 RequestSuccess = "RIDSuccess" 35 RequestInternalError = "RIDInternal" 36 RequestMalFormedResponse = "RIDMalFormed" 37 ) 38 39 const ( 40 UUID1 = strfmt.UUID("73f2eb5f-5abf-447a-81ca-74b1dd168241") 41 UUID2 = strfmt.UUID("73f2eb5f-5abf-447a-81ca-74b1dd168242") 42 ) 43 44 type fakeServer struct { 45 method string 46 path string 47 RequestError replica.SimpleResponse 48 RequestSuccess replica.SimpleResponse 49 host string 50 } 51 52 func newFakeReplicationServer(t *testing.T, method, path string) *fakeServer { 53 return &fakeServer{ 54 method: method, 55 path: path, 56 RequestError: replica.SimpleResponse{Errors: []replica.Error{{Msg: "error"}}}, 57 RequestSuccess: replica.SimpleResponse{}, 58 } 59 } 60 61 func (f *fakeServer) server(t *testing.T) *httptest.Server { 62 handler := func(w http.ResponseWriter, r *http.Request) { 63 if r.Method != f.method { 64 t.Errorf("method want %s got %s", f.method, r.Method) 65 w.WriteHeader(http.StatusBadRequest) 66 return 67 } 68 if f.path != r.URL.Path { 69 t.Errorf("path want %s got %s", f.path, r.URL.Path) 70 w.WriteHeader(http.StatusBadRequest) 71 return 72 } 73 requestID := r.URL.Query().Get(replica.RequestKey) 74 switch requestID { 75 case RequestInternalError: 76 w.WriteHeader(http.StatusInternalServerError) 77 case RequestError: 78 bytes, _ := json.Marshal(&f.RequestError) 79 w.Write(bytes) 80 case RequestSuccess: 81 bytes, _ := json.Marshal(&replica.SimpleResponse{}) 82 w.Write(bytes) 83 case RequestMalFormedResponse: 84 w.Write([]byte(`mal formed`)) 85 } 86 } 87 serv := httptest.NewServer(http.HandlerFunc(handler)) 88 f.host = serv.URL[7:] 89 return serv 90 } 91 92 func anyObject(uuid strfmt.UUID) models.Object { 93 return models.Object{ 94 Class: "C1", 95 CreationTimeUnix: 900000000001, 96 LastUpdateTimeUnix: 900000000002, 97 ID: uuid, 98 Properties: map[string]interface{}{ 99 "stringProp": "string", 100 "textProp": "text", 101 "datePropArray": []string{"1980-01-01T00:00:00+02:00"}, 102 }, 103 } 104 } 105 106 func TestReplicationPutObject(t *testing.T) { 107 t.Parallel() 108 109 ctx := context.Background() 110 f := newFakeReplicationServer(t, http.MethodPost, "/replicas/indices/C1/shards/S1/objects") 111 ts := f.server(t) 112 defer ts.Close() 113 114 client := newReplicationClient(ts.Client()) 115 t.Run("EncodeRequest", func(t *testing.T) { 116 obj := &storobj.Object{} 117 _, err := client.PutObject(ctx, "Node1", "C1", "S1", "RID", obj) 118 assert.NotNil(t, err) 119 assert.Contains(t, err.Error(), "encode") 120 }) 121 122 obj := &storobj.Object{MarshallerVersion: 1, Object: anyObject(UUID1)} 123 t.Run("ConnectionError", func(t *testing.T) { 124 _, err := client.PutObject(ctx, "", "C1", "S1", "", obj) 125 assert.NotNil(t, err) 126 assert.Contains(t, err.Error(), "connect") 127 }) 128 129 t.Run("Error", func(t *testing.T) { 130 resp, err := client.PutObject(ctx, f.host, "C1", "S1", RequestError, obj) 131 assert.Nil(t, err) 132 assert.Equal(t, replica.SimpleResponse{Errors: f.RequestError.Errors}, resp) 133 }) 134 135 t.Run("DecodeResponse", func(t *testing.T) { 136 _, err := client.PutObject(ctx, f.host, "C1", "S1", RequestMalFormedResponse, obj) 137 assert.NotNil(t, err) 138 assert.Contains(t, err.Error(), "decode response") 139 }) 140 141 t.Run("ServerInternalError", func(t *testing.T) { 142 _, err := client.PutObject(ctx, f.host, "C1", "S1", RequestInternalError, obj) 143 assert.NotNil(t, err) 144 assert.Contains(t, err.Error(), "status code") 145 }) 146 } 147 148 func TestReplicationDeleteObject(t *testing.T) { 149 t.Parallel() 150 151 ctx := context.Background() 152 uuid := UUID1 153 path := "/replicas/indices/C1/shards/S1/objects/" + uuid.String() 154 fs := newFakeReplicationServer(t, http.MethodDelete, path) 155 ts := fs.server(t) 156 defer ts.Close() 157 158 client := newReplicationClient(ts.Client()) 159 t.Run("ConnectionError", func(t *testing.T) { 160 _, err := client.DeleteObject(ctx, "", "C1", "S1", "", uuid) 161 assert.NotNil(t, err) 162 assert.Contains(t, err.Error(), "connect") 163 }) 164 165 t.Run("Error", func(t *testing.T) { 166 resp, err := client.DeleteObject(ctx, fs.host, "C1", "S1", RequestError, uuid) 167 assert.Nil(t, err) 168 assert.Equal(t, replica.SimpleResponse{Errors: fs.RequestError.Errors}, resp) 169 }) 170 171 t.Run("DecodeResponse", func(t *testing.T) { 172 _, err := client.DeleteObject(ctx, fs.host, "C1", "S1", RequestMalFormedResponse, uuid) 173 assert.NotNil(t, err) 174 assert.Contains(t, err.Error(), "decode response") 175 }) 176 177 t.Run("ServerInternalError", func(t *testing.T) { 178 _, err := client.DeleteObject(ctx, fs.host, "C1", "S1", RequestInternalError, uuid) 179 assert.NotNil(t, err) 180 assert.Contains(t, err.Error(), "status code") 181 }) 182 } 183 184 func TestReplicationPutObjects(t *testing.T) { 185 t.Parallel() 186 ctx := context.Background() 187 fs := newFakeReplicationServer(t, http.MethodPost, "/replicas/indices/C1/shards/S1/objects") 188 fs.RequestError.Errors = append(fs.RequestError.Errors, replica.Error{Msg: "error2"}) 189 ts := fs.server(t) 190 defer ts.Close() 191 192 client := newReplicationClient(ts.Client()) 193 t.Run("EncodeRequest", func(t *testing.T) { 194 objs := []*storobj.Object{{}} 195 _, err := client.PutObjects(ctx, "Node1", "C1", "S1", "RID", objs) 196 assert.NotNil(t, err) 197 assert.Contains(t, err.Error(), "encode") 198 }) 199 200 objects := []*storobj.Object{ 201 {MarshallerVersion: 1, Object: anyObject(UUID1)}, 202 {MarshallerVersion: 1, Object: anyObject(UUID2)}, 203 } 204 205 t.Run("ConnectionError", func(t *testing.T) { 206 _, err := client.PutObjects(ctx, "", "C1", "S1", "", objects) 207 assert.NotNil(t, err) 208 assert.Contains(t, err.Error(), "connect") 209 }) 210 211 t.Run("Error", func(t *testing.T) { 212 resp, err := client.PutObjects(ctx, fs.host, "C1", "S1", RequestError, objects) 213 assert.Nil(t, err) 214 assert.Equal(t, replica.SimpleResponse{Errors: fs.RequestError.Errors}, resp) 215 }) 216 217 t.Run("DecodeResponse", func(t *testing.T) { 218 _, err := client.PutObjects(ctx, fs.host, "C1", "S1", RequestMalFormedResponse, objects) 219 assert.NotNil(t, err) 220 assert.Contains(t, err.Error(), "decode response") 221 }) 222 223 t.Run("ServerInternalError", func(t *testing.T) { 224 _, err := client.PutObjects(ctx, fs.host, "C1", "S1", RequestInternalError, objects) 225 assert.NotNil(t, err) 226 assert.Contains(t, err.Error(), "status code") 227 }) 228 } 229 230 func TestReplicationMergeObject(t *testing.T) { 231 t.Parallel() 232 ctx := context.Background() 233 uuid := UUID1 234 f := newFakeReplicationServer(t, http.MethodPatch, "/replicas/indices/C1/shards/S1/objects/"+uuid.String()) 235 ts := f.server(t) 236 defer ts.Close() 237 238 client := newReplicationClient(ts.Client()) 239 doc := &objects.MergeDocument{ID: uuid} 240 t.Run("ConnectionError", func(t *testing.T) { 241 _, err := client.MergeObject(ctx, "", "C1", "S1", "", doc) 242 assert.NotNil(t, err) 243 assert.Contains(t, err.Error(), "connect") 244 }) 245 246 t.Run("Error", func(t *testing.T) { 247 resp, err := client.MergeObject(ctx, f.host, "C1", "S1", RequestError, doc) 248 assert.Nil(t, err) 249 assert.Equal(t, replica.SimpleResponse{Errors: f.RequestError.Errors}, resp) 250 }) 251 252 t.Run("DecodeResponse", func(t *testing.T) { 253 _, err := client.MergeObject(ctx, f.host, "C1", "S1", RequestMalFormedResponse, doc) 254 assert.NotNil(t, err) 255 assert.Contains(t, err.Error(), "decode response") 256 }) 257 258 t.Run("ServerInternalError", func(t *testing.T) { 259 _, err := client.MergeObject(ctx, f.host, "C1", "S1", RequestInternalError, doc) 260 assert.NotNil(t, err) 261 assert.Contains(t, err.Error(), "status code") 262 }) 263 } 264 265 func TestReplicationAddReferences(t *testing.T) { 266 t.Parallel() 267 268 ctx := context.Background() 269 fs := newFakeReplicationServer(t, http.MethodPost, "/replicas/indices/C1/shards/S1/objects/references") 270 fs.RequestError.Errors = append(fs.RequestError.Errors, replica.Error{Msg: "error2"}) 271 ts := fs.server(t) 272 defer ts.Close() 273 274 client := newReplicationClient(ts.Client()) 275 refs := []objects.BatchReference{{OriginalIndex: 1}, {OriginalIndex: 2}} 276 t.Run("ConnectionError", func(t *testing.T) { 277 _, err := client.AddReferences(ctx, "", "C1", "S1", "", refs) 278 assert.NotNil(t, err) 279 assert.Contains(t, err.Error(), "connect") 280 }) 281 282 t.Run("Error", func(t *testing.T) { 283 resp, err := client.AddReferences(ctx, fs.host, "C1", "S1", RequestError, refs) 284 assert.Nil(t, err) 285 assert.Equal(t, replica.SimpleResponse{Errors: fs.RequestError.Errors}, resp) 286 }) 287 288 t.Run("DecodeResponse", func(t *testing.T) { 289 _, err := client.AddReferences(ctx, fs.host, "C1", "S1", RequestMalFormedResponse, refs) 290 assert.NotNil(t, err) 291 assert.Contains(t, err.Error(), "decode response") 292 }) 293 294 t.Run("ServerInternalError", func(t *testing.T) { 295 _, err := client.AddReferences(ctx, fs.host, "C1", "S1", RequestInternalError, refs) 296 assert.NotNil(t, err) 297 assert.Contains(t, err.Error(), "status code") 298 }) 299 } 300 301 func TestReplicationDeleteObjects(t *testing.T) { 302 t.Parallel() 303 304 ctx := context.Background() 305 fs := newFakeReplicationServer(t, http.MethodDelete, "/replicas/indices/C1/shards/S1/objects") 306 fs.RequestError.Errors = append(fs.RequestError.Errors, replica.Error{Msg: "error2"}) 307 ts := fs.server(t) 308 defer ts.Close() 309 client := newReplicationClient(ts.Client()) 310 311 uuids := []strfmt.UUID{strfmt.UUID("1"), strfmt.UUID("2")} 312 t.Run("ConnectionError", func(t *testing.T) { 313 _, err := client.DeleteObjects(ctx, "", "C1", "S1", "", uuids, false) 314 assert.NotNil(t, err) 315 assert.Contains(t, err.Error(), "connect") 316 }) 317 318 t.Run("Error", func(t *testing.T) { 319 resp, err := client.DeleteObjects(ctx, fs.host, "C1", "S1", RequestError, uuids, false) 320 assert.Nil(t, err) 321 assert.Equal(t, replica.SimpleResponse{Errors: fs.RequestError.Errors}, resp) 322 }) 323 324 t.Run("DecodeResponse", func(t *testing.T) { 325 _, err := client.DeleteObjects(ctx, fs.host, "C1", "S1", RequestMalFormedResponse, uuids, false) 326 assert.NotNil(t, err) 327 assert.Contains(t, err.Error(), "decode response") 328 }) 329 330 t.Run("ServerInternalError", func(t *testing.T) { 331 _, err := client.DeleteObjects(ctx, fs.host, "C1", "S1", RequestInternalError, uuids, false) 332 assert.NotNil(t, err) 333 assert.Contains(t, err.Error(), "status code") 334 }) 335 } 336 337 func TestReplicationAbort(t *testing.T) { 338 t.Parallel() 339 340 ctx := context.Background() 341 path := "/replicas/indices/C1/shards/S1:abort" 342 fs := newFakeReplicationServer(t, http.MethodPost, path) 343 ts := fs.server(t) 344 defer ts.Close() 345 client := newReplicationClient(ts.Client()) 346 347 t.Run("ConnectionError", func(t *testing.T) { 348 client := newReplicationClient(ts.Client()) 349 client.maxBackOff = client.timeoutUnit * 20 350 _, err := client.Abort(ctx, "", "C1", "S1", "") 351 assert.NotNil(t, err) 352 assert.Contains(t, err.Error(), "connect") 353 }) 354 355 t.Run("Error", func(t *testing.T) { 356 resp, err := client.Abort(ctx, fs.host, "C1", "S1", RequestError) 357 assert.Nil(t, err) 358 assert.Equal(t, replica.SimpleResponse{Errors: fs.RequestError.Errors}, resp) 359 }) 360 361 t.Run("DecodeResponse", func(t *testing.T) { 362 _, err := client.Abort(ctx, fs.host, "C1", "S1", RequestMalFormedResponse) 363 assert.NotNil(t, err) 364 assert.Contains(t, err.Error(), "decode response") 365 }) 366 client.timeoutUnit = client.maxBackOff * 3 367 t.Run("ServerInternalError", func(t *testing.T) { 368 _, err := client.Abort(ctx, fs.host, "C1", "S1", RequestInternalError) 369 assert.NotNil(t, err) 370 assert.Contains(t, err.Error(), "status code") 371 }) 372 } 373 374 func TestReplicationCommit(t *testing.T) { 375 t.Parallel() 376 377 ctx := context.Background() 378 path := "/replicas/indices/C1/shards/S1:commit" 379 fs := newFakeReplicationServer(t, http.MethodPost, path) 380 ts := fs.server(t) 381 defer ts.Close() 382 resp := replica.SimpleResponse{} 383 client := newReplicationClient(ts.Client()) 384 385 t.Run("ConnectionError", func(t *testing.T) { 386 err := client.Commit(ctx, "", "C1", "S1", "", &resp) 387 assert.NotNil(t, err) 388 assert.Contains(t, err.Error(), "connect") 389 }) 390 391 t.Run("Error", func(t *testing.T) { 392 err := client.Commit(ctx, fs.host, "C1", "S1", RequestError, &resp) 393 assert.Nil(t, err) 394 assert.Equal(t, replica.SimpleResponse{Errors: fs.RequestError.Errors}, resp) 395 }) 396 397 t.Run("DecodeResponse", func(t *testing.T) { 398 err := client.Commit(ctx, fs.host, "C1", "S1", RequestMalFormedResponse, &resp) 399 assert.NotNil(t, err) 400 assert.Contains(t, err.Error(), "decode response") 401 }) 402 403 t.Run("ServerInternalError", func(t *testing.T) { 404 err := client.Commit(ctx, fs.host, "C1", "S1", RequestInternalError, &resp) 405 assert.NotNil(t, err) 406 assert.Contains(t, err.Error(), "status code") 407 }) 408 } 409 410 func TestReplicationFetchObject(t *testing.T) { 411 t.Parallel() 412 413 expected := objects.Replica{ 414 ID: UUID1, 415 Object: &storobj.Object{ 416 MarshallerVersion: 1, 417 Object: models.Object{ 418 ID: UUID1, 419 Properties: map[string]interface{}{ 420 "stringProp": "abc", 421 }, 422 }, 423 Vector: []float32{1, 2, 3, 4, 5}, 424 VectorLen: 5, 425 }, 426 } 427 428 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 429 b, _ := expected.MarshalBinary() 430 w.Write(b) 431 })) 432 433 c := newReplicationClient(server.Client()) 434 resp, err := c.FetchObject(context.Background(), server.URL[7:], 435 "C1", "S1", expected.ID, nil, additional.Properties{}) 436 require.Nil(t, err) 437 assert.Equal(t, expected.ID, resp.ID) 438 assert.Equal(t, expected.Deleted, resp.Deleted) 439 assert.EqualValues(t, expected.Object, resp.Object) 440 } 441 442 func TestReplicationFetchObjects(t *testing.T) { 443 t.Parallel() 444 expected := objects.Replicas{ 445 { 446 ID: UUID1, 447 Object: &storobj.Object{ 448 MarshallerVersion: 1, 449 Object: models.Object{ 450 ID: UUID1, 451 Properties: map[string]interface{}{ 452 "stringProp": "abc", 453 }, 454 }, 455 Vector: []float32{1, 2, 3, 4, 5}, 456 VectorLen: 5, 457 }, 458 }, 459 { 460 ID: UUID2, 461 Object: &storobj.Object{ 462 MarshallerVersion: 1, 463 Object: models.Object{ 464 ID: UUID2, 465 Properties: map[string]interface{}{ 466 "floatProp": float64(123), 467 }, 468 }, 469 Vector: []float32{10, 20, 30, 40, 50}, 470 VectorLen: 5, 471 }, 472 }, 473 } 474 475 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 476 b, _ := expected.MarshalBinary() 477 w.Write(b) 478 })) 479 480 c := newReplicationClient(server.Client()) 481 resp, err := c.FetchObjects(context.Background(), server.URL[7:], "C1", "S1", []strfmt.UUID{expected[0].ID}) 482 require.Nil(t, err) 483 require.Len(t, resp, 2) 484 assert.Equal(t, expected[0].ID, resp[0].ID) 485 assert.Equal(t, expected[0].Deleted, resp[0].Deleted) 486 assert.EqualValues(t, expected[0].Object, resp[0].Object) 487 assert.Equal(t, expected[1].ID, resp[1].ID) 488 assert.Equal(t, expected[1].Deleted, resp[1].Deleted) 489 assert.EqualValues(t, expected[1].Object, resp[1].Object) 490 } 491 492 func TestReplicationDigestObjects(t *testing.T) { 493 t.Parallel() 494 495 now := time.Now() 496 expected := []replica.RepairResponse{ 497 { 498 ID: UUID1.String(), 499 UpdateTime: now.UnixMilli(), 500 Version: 1, 501 }, 502 { 503 ID: UUID2.String(), 504 UpdateTime: now.UnixMilli(), 505 Version: 1, 506 }, 507 } 508 509 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 510 b, _ := json.Marshal(expected) 511 w.Write(b) 512 })) 513 514 c := newReplicationClient(server.Client()) 515 resp, err := c.DigestObjects(context.Background(), server.URL[7:], "C1", "S1", []strfmt.UUID{ 516 strfmt.UUID(expected[0].ID), 517 strfmt.UUID(expected[1].ID), 518 }) 519 require.Nil(t, err) 520 require.Len(t, resp, 2) 521 assert.Equal(t, expected[0].ID, resp[0].ID) 522 assert.Equal(t, expected[0].Deleted, resp[0].Deleted) 523 assert.Equal(t, expected[0].UpdateTime, resp[0].UpdateTime) 524 assert.Equal(t, expected[0].Version, resp[0].Version) 525 assert.Equal(t, expected[1].ID, resp[1].ID) 526 assert.Equal(t, expected[1].Deleted, resp[1].Deleted) 527 assert.Equal(t, expected[1].UpdateTime, resp[1].UpdateTime) 528 assert.Equal(t, expected[1].Version, resp[1].Version) 529 } 530 531 func TestReplicationOverwriteObjects(t *testing.T) { 532 t.Parallel() 533 534 now := time.Now() 535 input := []*objects.VObject{ 536 { 537 LatestObject: &models.Object{ 538 ID: UUID1, 539 Class: "C1", 540 CreationTimeUnix: now.UnixMilli(), 541 LastUpdateTimeUnix: now.Add(time.Hour).UnixMilli(), 542 Properties: map[string]interface{}{ 543 "stringProp": "abc", 544 }, 545 Vector: []float32{1, 2, 3, 4, 5}, 546 }, 547 StaleUpdateTime: now.UnixMilli(), 548 Version: 0, 549 }, 550 } 551 expected := []replica.RepairResponse{ 552 { 553 ID: UUID1.String(), 554 Version: 1, 555 UpdateTime: now.Add(time.Hour).UnixMilli(), 556 }, 557 } 558 559 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 560 b, _ := json.Marshal(expected) 561 w.Write(b) 562 })) 563 564 c := newReplicationClient(server.Client()) 565 resp, err := c.OverwriteObjects(context.Background(), server.URL[7:], "C1", "S1", input) 566 require.Nil(t, err) 567 require.Len(t, resp, 1) 568 assert.Equal(t, expected[0].ID, resp[0].ID) 569 assert.Equal(t, expected[0].Version, resp[0].Version) 570 assert.Equal(t, expected[0].UpdateTime, resp[0].UpdateTime) 571 } 572 573 func TestExpBackOff(t *testing.T) { 574 N := 200 575 av := time.Duration(0) 576 delay := time.Nanosecond * 20 577 for i := 0; i < N; i++ { 578 av += backOff(delay) 579 } 580 av /= time.Duration(N) 581 if av < time.Nanosecond*30 || av > time.Nanosecond*50 { 582 t.Errorf("average time got %v", av) 583 } 584 } 585 586 func newReplicationClient(httpClient *http.Client) *replicationClient { 587 c := NewReplicationClient(httpClient).(*replicationClient) 588 c.minBackOff = time.Millisecond * 1 589 c.maxBackOff = time.Millisecond * 8 590 c.timeoutUnit = time.Millisecond * 20 591 return c 592 }