github.com/weaviate/weaviate@v1.24.6/test/acceptance/replication/crud_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 replication 13 14 import ( 15 "context" 16 "encoding/json" 17 "fmt" 18 "testing" 19 "time" 20 21 "github.com/go-openapi/strfmt" 22 "github.com/stretchr/testify/assert" 23 "github.com/stretchr/testify/require" 24 "github.com/weaviate/weaviate/client/objects" 25 "github.com/weaviate/weaviate/entities/models" 26 "github.com/weaviate/weaviate/entities/schema/crossref" 27 "github.com/weaviate/weaviate/test/docker" 28 "github.com/weaviate/weaviate/test/helper" 29 "github.com/weaviate/weaviate/test/helper/sample-schema/articles" 30 "github.com/weaviate/weaviate/usecases/replica" 31 ) 32 33 var ( 34 paragraphIDs = []strfmt.UUID{ 35 strfmt.UUID("3bf331ac-8c86-4f95-b127-2f8f96bbc093"), 36 strfmt.UUID("47b26ba1-6bc9-41f8-a655-8b9a5b60e1a3"), 37 strfmt.UUID("5fef6289-28d2-4ea2-82a9-48eb501200cd"), 38 strfmt.UUID("34a673b4-8859-4cb4-bb30-27f5622b47e9"), 39 strfmt.UUID("9fa362f5-c2dc-4fb8-b5b2-11701adc5f75"), 40 strfmt.UUID("63735238-6723-4caf-9eaa-113120968ff4"), 41 strfmt.UUID("2236744d-b2d2-40e5-95d8-2574f20a7126"), 42 strfmt.UUID("1a54e25d-aaf9-48d2-bc3c-bef00b556297"), 43 strfmt.UUID("0b8a0e70-a240-44b2-ac6d-26dda97523b9"), 44 strfmt.UUID("50566856-5d0a-4fb1-a390-e099bc236f66"), 45 } 46 47 articleIDs = []strfmt.UUID{ 48 strfmt.UUID("aeaf8743-5a8f-4149-b960-444181d3131a"), 49 strfmt.UUID("2a1e9834-064e-4ca8-9efc-35707c6bae6d"), 50 strfmt.UUID("8d101c0c-4deb-48d0-805c-d9c691042a1a"), 51 strfmt.UUID("b9715fec-ef6c-4e8d-a89e-55e2eebee3f6"), 52 strfmt.UUID("faf520f2-f6c3-4cdf-9c16-0348ffd0f8ac"), 53 strfmt.UUID("d4c695dd-4dc7-4e49-bc73-089ef5f90fc8"), 54 strfmt.UUID("c7949324-e07f-4ffc-8be0-194f0470d375"), 55 strfmt.UUID("9c112e01-7759-43ed-a6e8-5defb267c8ee"), 56 strfmt.UUID("9bf847f3-3a1a-45a5-b656-311163e536b5"), 57 strfmt.UUID("c1975388-d67c-404a-ae77-5983fbaea4bb"), 58 } 59 ) 60 61 func immediateReplicaCRUD(t *testing.T) { 62 ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) 63 defer cancel() 64 65 compose, err := docker.New(). 66 WithWeaviateCluster(). 67 WithText2VecContextionary(). 68 Start(ctx) 69 require.Nil(t, err) 70 defer func() { 71 if err := compose.Terminate(ctx); err != nil { 72 t.Fatalf("failed to terminate test containers: %s", err.Error()) 73 } 74 }() 75 76 helper.SetupClient(compose.GetWeaviate().URI()) 77 paragraphClass := articles.ParagraphsClass() 78 articleClass := articles.ArticlesClass() 79 80 t.Run("create schema", func(t *testing.T) { 81 paragraphClass.ReplicationConfig = &models.ReplicationConfig{ 82 Factor: 2, 83 } 84 helper.CreateClass(t, paragraphClass) 85 articleClass.ReplicationConfig = &models.ReplicationConfig{ 86 Factor: 2, 87 } 88 helper.CreateClass(t, articleClass) 89 }) 90 91 t.Run("insert paragraphs batch", func(t *testing.T) { 92 t.Run("create objects on node 1", func(t *testing.T) { 93 batch := make([]*models.Object, len(paragraphIDs)) 94 for i, id := range paragraphIDs { 95 batch[i] = articles.NewParagraph(). 96 WithID(id). 97 WithContents(fmt.Sprintf("paragraph#%d", i)). 98 Object() 99 } 100 createObjects(t, compose.GetWeaviate().URI(), batch) 101 }) 102 103 t.Run("stop node 1", func(t *testing.T) { 104 stopNode(ctx, t, compose, compose.GetWeaviate().Name()) 105 }) 106 107 t.Run("assert objects exist on node 2", func(t *testing.T) { 108 resp := gqlGet(t, compose.GetWeaviateNode2().URI(), "Paragraph", replica.One) 109 assert.Len(t, resp, len(paragraphIDs)) 110 }) 111 112 t.Run("restart node 1", func(t *testing.T) { 113 restartNode1(ctx, t, compose) 114 }) 115 }) 116 117 t.Run("insert articles individually", func(t *testing.T) { 118 t.Run("create objects on node 2", func(t *testing.T) { 119 for i, id := range articleIDs { 120 obj := articles.NewArticle(). 121 WithID(id). 122 WithTitle(fmt.Sprintf("Article#%d", i)). 123 Object() 124 createObject(t, compose.GetWeaviateNode2().URI(), obj) 125 } 126 }) 127 128 t.Run("stop node 2", func(t *testing.T) { 129 stopNode(ctx, t, compose, compose.GetWeaviateNode2().Name()) 130 }) 131 132 t.Run("assert objects exist on node 1", func(t *testing.T) { 133 resp := gqlGet(t, compose.GetWeaviate().URI(), "Article", replica.One) 134 assert.Len(t, resp, len(articleIDs)) 135 }) 136 137 t.Run("restart node 2", func(t *testing.T) { 138 err = compose.Start(ctx, compose.GetWeaviateNode2().Name()) 139 require.Nil(t, err) 140 }) 141 }) 142 143 t.Run("add references", func(t *testing.T) { 144 refs := make([]*models.BatchReference, len(articleIDs)) 145 for i := range articleIDs { 146 refs[i] = &models.BatchReference{ 147 From: strfmt.URI(crossref.NewSource("Article", "hasParagraphs", articleIDs[i]).String()), 148 To: strfmt.URI(crossref.NewLocalhost("Paragraph", paragraphIDs[i]).String()), 149 } 150 } 151 152 t.Run("add references to node 1", func(t *testing.T) { 153 addReferences(t, compose.GetWeaviate().URI(), refs) 154 }) 155 156 t.Run("stop node 1", func(t *testing.T) { 157 stopNode(ctx, t, compose, compose.GetWeaviate().Name()) 158 }) 159 160 t.Run("assert references were added successfully to node 2", func(t *testing.T) { 161 type additional struct { 162 ID strfmt.UUID `json:"id"` 163 } 164 165 type article struct { 166 Additional additional `json:"_additional"` 167 HasParagraphs []struct { 168 Additional additional `json:"_additional"` 169 } `json:"hasParagraphs"` 170 } 171 172 // maps article id to referenced paragraph id 173 refPairs := make(map[strfmt.UUID]strfmt.UUID) 174 resp := gqlGet(t, compose.GetWeaviateNode2().URI(), "Article", replica.One, 175 "_additional{id}", "hasParagraphs {... on Paragraph {_additional{id}}}") 176 assert.Len(t, resp, len(articleIDs)) 177 178 for _, r := range resp { 179 b, err := json.Marshal(r) 180 require.Nil(t, err) 181 var art article 182 err = json.Unmarshal(b, &art) 183 require.Nil(t, err) 184 require.Len(t, art.HasParagraphs, 1) 185 refPairs[art.Additional.ID] = art.HasParagraphs[0].Additional.ID 186 } 187 188 for i := range articleIDs { 189 paragraphID, ok := refPairs[articleIDs[i]] 190 require.True(t, ok, "expected %q to be in refPairs: %+v", articleIDs[i], refPairs) 191 assert.Equal(t, paragraphIDs[i], paragraphID) 192 } 193 }) 194 195 t.Run("restart node 1", func(t *testing.T) { 196 restartNode1(ctx, t, compose) 197 }) 198 }) 199 200 t.Run("patch an object", func(t *testing.T) { 201 before, err := getObject(t, compose.GetWeaviate().URI(), "Article", articleIDs[0], false) 202 require.Nil(t, err) 203 newTitle := "Article#9000" 204 205 t.Run("execute object patch on node 2", func(t *testing.T) { 206 patch := &models.Object{ 207 ID: before.ID, 208 Class: "Article", 209 Properties: map[string]interface{}{"title": newTitle}, 210 } 211 patchObject(t, compose.GetWeaviateNode2().URI(), patch) 212 }) 213 214 t.Run("stop node 2", func(t *testing.T) { 215 stopNode(ctx, t, compose, compose.GetWeaviateNode2().Name()) 216 }) 217 218 t.Run("assert object is patched on node 1", func(t *testing.T) { 219 after, err := getObjectFromNode(t, compose.GetWeaviate().URI(), "Article", articleIDs[0], "node1") 220 require.Nil(t, err) 221 222 newVal, ok := after.Properties.(map[string]interface{})["title"] 223 require.True(t, ok) 224 assert.Equal(t, newTitle, newVal) 225 }) 226 227 t.Run("restart node 2", func(t *testing.T) { 228 err = compose.Start(ctx, compose.GetWeaviateNode2().Name()) 229 require.Nil(t, err) 230 }) 231 }) 232 233 t.Run("delete an object", func(t *testing.T) { 234 t.Run("execute delete object on node 1", func(t *testing.T) { 235 deleteObject(t, compose.GetWeaviate().URI(), "Article", articleIDs[0]) 236 }) 237 238 t.Run("stop node 1", func(t *testing.T) { 239 stopNode(ctx, t, compose, compose.GetWeaviate().Name()) 240 }) 241 242 t.Run("assert object removed from node 2", func(t *testing.T) { 243 _, err := getObjectFromNode(t, compose.GetWeaviateNode2().URI(), "Article", articleIDs[0], "node2") 244 assert.Equal(t, &objects.ObjectsClassGetNotFound{}, err) 245 }) 246 247 t.Run("restart node 1", func(t *testing.T) { 248 restartNode1(ctx, t, compose) 249 }) 250 }) 251 252 t.Run("batch delete all objects", func(t *testing.T) { 253 t.Run("execute batch delete on node 2", func(t *testing.T) { 254 deleteObjects(t, compose.GetWeaviateNode2().URI(), 255 "Article", []string{"title"}, "Article#*") 256 }) 257 258 t.Run("stop node 2", func(t *testing.T) { 259 stopNode(ctx, t, compose, compose.GetWeaviateNode2().Name()) 260 }) 261 262 t.Run("assert objects are removed from node 1", func(t *testing.T) { 263 resp := gqlGet(t, compose.GetWeaviate().URI(), "Article", replica.One) 264 assert.Empty(t, resp) 265 }) 266 267 t.Run("restart node 2", func(t *testing.T) { 268 err = compose.Start(ctx, compose.GetWeaviateNode2().Name()) 269 require.Nil(t, err) 270 }) 271 }) 272 } 273 274 func eventualReplicaCRUD(t *testing.T) { 275 ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) 276 defer cancel() 277 278 compose, err := docker.New(). 279 WithWeaviateCluster(). 280 WithText2VecContextionary(). 281 Start(ctx) 282 require.Nil(t, err) 283 defer func() { 284 if err := compose.Terminate(ctx); err != nil { 285 t.Fatalf("failed to terminate test containers: %s", err.Error()) 286 } 287 }() 288 289 helper.SetupClient(compose.GetWeaviate().URI()) 290 paragraphClass := articles.ParagraphsClass() 291 articleClass := articles.ArticlesClass() 292 293 t.Run("create schema on node 1", func(t *testing.T) { 294 paragraphClass.ShardingConfig = map[string]interface{}{"desiredCount": 1} 295 helper.CreateClass(t, paragraphClass) 296 articleClass.ShardingConfig = map[string]interface{}{"desiredCount": 1} 297 helper.CreateClass(t, articleClass) 298 }) 299 300 t.Run("insert paragraphs batch on node 1", func(t *testing.T) { 301 batch := make([]*models.Object, len(paragraphIDs)) 302 for i, id := range paragraphIDs { 303 batch[i] = articles.NewParagraph(). 304 WithID(id). 305 WithContents(fmt.Sprintf("paragraph#%d", i)). 306 Object() 307 } 308 createObjects(t, compose.GetWeaviate().URI(), batch) 309 }) 310 311 t.Run("insert articles batch on node 1", func(t *testing.T) { 312 batch := make([]*models.Object, len(articleIDs)) 313 for i, id := range articleIDs { 314 batch[i] = articles.NewArticle(). 315 WithID(id). 316 WithTitle(fmt.Sprintf("Article#%d", i)). 317 Object() 318 } 319 createObjects(t, compose.GetWeaviate().URI(), batch) 320 }) 321 322 t.Run("configure classes to replicate to node 2", func(t *testing.T) { 323 ac := helper.GetClass(t, "Article") 324 ac.ReplicationConfig = &models.ReplicationConfig{ 325 Factor: 2, 326 } 327 helper.UpdateClass(t, ac) 328 329 pc := helper.GetClass(t, "Paragraph") 330 pc.ReplicationConfig = &models.ReplicationConfig{ 331 Factor: 2, 332 } 333 helper.UpdateClass(t, pc) 334 }) 335 336 t.Run("stop node 1", func(t *testing.T) { 337 stopNode(ctx, t, compose, compose.GetWeaviate().Name()) 338 }) 339 340 t.Run("assert all previous data replicated to node 2", func(t *testing.T) { 341 resp := gqlGet(t, compose.GetWeaviateNode2().URI(), "Article", replica.One) 342 assert.Len(t, resp, len(articleIDs)) 343 resp = gqlGet(t, compose.GetWeaviateNode2().URI(), "Paragraph", replica.One) 344 assert.Len(t, resp, len(paragraphIDs)) 345 }) 346 347 t.Run("restart node 1", func(t *testing.T) { 348 restartNode1(ctx, t, compose) 349 }) 350 351 t.Run("assert any future writes are replicated", func(t *testing.T) { 352 t.Run("patch an object", func(t *testing.T) { 353 before, err := getObject(t, compose.GetWeaviate().URI(), "Article", articleIDs[0], false) 354 require.Nil(t, err) 355 newTitle := "Article#9000" 356 357 t.Run("execute object patch on node 2", func(t *testing.T) { 358 patch := &models.Object{ 359 ID: before.ID, 360 Class: "Article", 361 Properties: map[string]interface{}{"title": newTitle}, 362 } 363 patchObject(t, compose.GetWeaviateNode2().URI(), patch) 364 }) 365 366 t.Run("stop node 2", func(t *testing.T) { 367 stopNode(ctx, t, compose, compose.GetWeaviateNode2().Name()) 368 }) 369 370 t.Run("assert object is patched on node 1", func(t *testing.T) { 371 after, err := getObjectFromNode(t, compose.GetWeaviate().URI(), "Article", articleIDs[0], "node1") 372 require.Nil(t, err) 373 374 newVal, ok := after.Properties.(map[string]interface{})["title"] 375 require.True(t, ok) 376 assert.Equal(t, newTitle, newVal) 377 }) 378 379 t.Run("restart node 2", func(t *testing.T) { 380 err = compose.Start(ctx, compose.GetWeaviateNode2().Name()) 381 require.Nil(t, err) 382 }) 383 }) 384 385 t.Run("delete an object", func(t *testing.T) { 386 t.Run("execute delete object on node 1", func(t *testing.T) { 387 deleteObject(t, compose.GetWeaviate().URI(), "Article", articleIDs[0]) 388 }) 389 390 t.Run("stop node 1", func(t *testing.T) { 391 stopNode(ctx, t, compose, compose.GetWeaviate().Name()) 392 }) 393 394 t.Run("assert object removed from node 2", func(t *testing.T) { 395 _, err := getObjectFromNode(t, compose.GetWeaviateNode2().URI(), "Article", articleIDs[0], "node2") 396 assert.Equal(t, &objects.ObjectsClassGetNotFound{}, err) 397 }) 398 399 t.Run("restart node 1", func(t *testing.T) { 400 restartNode1(ctx, t, compose) 401 }) 402 }) 403 404 t.Run("batch delete all objects", func(t *testing.T) { 405 t.Run("execute batch delete on node 2", func(t *testing.T) { 406 deleteObjects(t, compose.GetWeaviateNode2().URI(), 407 "Article", []string{"title"}, "Article#*") 408 }) 409 410 t.Run("stop node 2", func(t *testing.T) { 411 stopNode(ctx, t, compose, compose.GetWeaviateNode2().Name()) 412 }) 413 414 t.Run("assert objects are removed from node 1", func(t *testing.T) { 415 resp := gqlGet(t, compose.GetWeaviate().URI(), "Article", replica.One) 416 assert.Empty(t, resp) 417 }) 418 419 t.Run("restart node 2", func(t *testing.T) { 420 err = compose.Start(ctx, compose.GetWeaviateNode2().Name()) 421 require.Nil(t, err) 422 }) 423 }) 424 }) 425 } 426 427 func restartNode1(ctx context.Context, t *testing.T, compose *docker.DockerCompose) { 428 // since node1 is the gossip "leader", node 2 must be stopped and restarted 429 // after node1 to re-facilitate internode communication 430 stopNode(ctx, t, compose, compose.GetWeaviateNode2().Name()) 431 require.Nil(t, compose.Start(ctx, compose.GetWeaviate().Name())) 432 require.Nil(t, compose.Start(ctx, compose.GetWeaviateNode2().Name())) 433 <-time.After(1 * time.Second) // wait for initialization 434 } 435 436 func stopNode(ctx context.Context, t *testing.T, compose *docker.DockerCompose, container string) { 437 require.Nil(t, compose.Stop(ctx, container, nil)) 438 <-time.After(1 * time.Second) // give time for shutdown 439 }