github.com/weaviate/weaviate@v1.24.6/test/acceptance/nodes/nodes_api_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 test 13 14 import ( 15 "context" 16 "fmt" 17 "testing" 18 "time" 19 20 "github.com/go-openapi/strfmt" 21 "github.com/stretchr/testify/assert" 22 "github.com/stretchr/testify/require" 23 "github.com/weaviate/weaviate/client/batch" 24 "github.com/weaviate/weaviate/client/meta" 25 "github.com/weaviate/weaviate/client/nodes" 26 "github.com/weaviate/weaviate/entities/models" 27 "github.com/weaviate/weaviate/entities/verbosity" 28 "github.com/weaviate/weaviate/test/docker" 29 "github.com/weaviate/weaviate/test/helper" 30 "github.com/weaviate/weaviate/test/helper/sample-schema/books" 31 "github.com/weaviate/weaviate/test/helper/sample-schema/documents" 32 "github.com/weaviate/weaviate/test/helper/sample-schema/multishard" 33 ) 34 35 func Test_NodesAPI(t *testing.T) { 36 t.Run("empty DB", func(t *testing.T) { 37 meta, err := helper.Client(t).Meta.MetaGet(meta.NewMetaGetParams(), nil) 38 require.Nil(t, err) 39 assert.NotNil(t, meta.GetPayload()) 40 41 assertions := func(t require.TestingT, nodeStatus *models.NodeStatus) { 42 require.NotNil(t, nodeStatus) 43 assert.Equal(t, models.NodeStatusStatusHEALTHY, *nodeStatus.Status) 44 assert.True(t, len(nodeStatus.Name) > 0) 45 assert.True(t, nodeStatus.GitHash != "" && nodeStatus.GitHash != "unknown") 46 assert.Equal(t, meta.Payload.Version, nodeStatus.Version) 47 assert.Empty(t, nodeStatus.Shards) 48 require.Nil(t, nodeStatus.Stats) 49 } 50 51 testStatusResponse(t, assertions, nil, "") 52 }) 53 54 t.Run("DB with Books (1 class ,1 shard configuration, 1 node)", func(t *testing.T) { 55 booksClass := books.ClassContextionaryVectorizer() 56 helper.CreateClass(t, booksClass) 57 defer helper.DeleteClass(t, booksClass.Class) 58 59 for _, book := range books.Objects() { 60 helper.CreateObject(t, book) 61 helper.AssertGetObjectEventually(t, book.Class, book.ID) 62 } 63 64 minimalAssertions := func(t require.TestingT, nodeStatus *models.NodeStatus) { 65 require.NotNil(t, nodeStatus) 66 assert.Equal(t, models.NodeStatusStatusHEALTHY, *nodeStatus.Status) 67 assert.True(t, len(nodeStatus.Name) > 0) 68 assert.True(t, nodeStatus.GitHash != "" && nodeStatus.GitHash != "unknown") 69 } 70 71 verboseAssertions := func(t require.TestingT, nodeStatus *models.NodeStatus) { 72 require.Len(t, nodeStatus.Shards, 1) 73 shard := nodeStatus.Shards[0] 74 assert.True(t, len(shard.Name) > 0) 75 assert.Equal(t, booksClass.Class, shard.Class) 76 assert.Equal(t, int64(3), shard.ObjectCount) 77 require.NotNil(t, nodeStatus.Stats) 78 assert.Equal(t, int64(3), nodeStatus.Stats.ObjectCount) 79 assert.Equal(t, int64(1), nodeStatus.Stats.ShardCount) 80 } 81 82 testStatusResponse(t, minimalAssertions, verboseAssertions, "") 83 }) 84 85 t.Run("DB with MultiShard (1 class, 2 shards configuration, 1 node)", func(t *testing.T) { 86 multiShardClass := multishard.ClassContextionaryVectorizer() 87 helper.CreateClass(t, multiShardClass) 88 defer helper.DeleteClass(t, multiShardClass.Class) 89 90 for _, multiShard := range multishard.Objects() { 91 helper.CreateObject(t, multiShard) 92 helper.AssertGetObjectEventually(t, multiShard.Class, multiShard.ID) 93 } 94 95 minimalAssertions := func(t require.TestingT, nodeStatus *models.NodeStatus) { 96 require.NotNil(t, nodeStatus) 97 assert.Equal(t, models.NodeStatusStatusHEALTHY, *nodeStatus.Status) 98 assert.True(t, len(nodeStatus.Name) > 0) 99 assert.True(t, nodeStatus.GitHash != "" && nodeStatus.GitHash != "unknown") 100 } 101 102 verboseAsssertions := func(t require.TestingT, nodeStatus *models.NodeStatus) { 103 assert.Len(t, nodeStatus.Shards, 2) 104 for _, shard := range nodeStatus.Shards { 105 assert.True(t, len(shard.Name) > 0) 106 assert.Equal(t, multiShardClass.Class, shard.Class) 107 assert.GreaterOrEqual(t, shard.ObjectCount, int64(0)) 108 require.NotNil(t, nodeStatus.Stats) 109 assert.Equal(t, int64(3), nodeStatus.Stats.ObjectCount) 110 assert.Equal(t, int64(2), nodeStatus.Stats.ShardCount) 111 } 112 } 113 114 testStatusResponse(t, minimalAssertions, verboseAsssertions, "") 115 }) 116 117 t.Run("with class name: DB with Books and Documents, 1 shard, 1 node", func(t *testing.T) { 118 booksClass := books.ClassContextionaryVectorizer() 119 helper.CreateClass(t, booksClass) 120 defer helper.DeleteClass(t, booksClass.Class) 121 122 t.Run("insert and check books", func(t *testing.T) { 123 for _, book := range books.Objects() { 124 helper.CreateObject(t, book) 125 helper.AssertGetObjectEventually(t, book.Class, book.ID) 126 } 127 128 minimalAssertions := func(t require.TestingT, nodeStatus *models.NodeStatus) {} 129 verboseAssertions := func(t require.TestingT, nodeStatus *models.NodeStatus) { 130 require.NotNil(t, nodeStatus.Stats) 131 assert.Equal(t, int64(3), nodeStatus.Stats.ObjectCount) 132 assert.Equal(t, int64(1), nodeStatus.Stats.ShardCount) 133 } 134 135 testStatusResponse(t, minimalAssertions, verboseAssertions, "") 136 }) 137 138 t.Run("insert and check documents", func(t *testing.T) { 139 docsClasses := documents.ClassesContextionaryVectorizer(false) 140 helper.CreateClass(t, docsClasses[0]) 141 helper.CreateClass(t, docsClasses[1]) 142 defer helper.DeleteClass(t, docsClasses[0].Class) 143 defer helper.DeleteClass(t, docsClasses[1].Class) 144 145 for _, doc := range documents.Objects() { 146 helper.CreateObject(t, doc) 147 helper.AssertGetObjectEventually(t, doc.Class, doc.ID) 148 } 149 150 docsClass := docsClasses[0] 151 152 minimalAssertions := func(t require.TestingT, nodeStatus *models.NodeStatus) { 153 assert.Equal(t, models.NodeStatusStatusHEALTHY, *nodeStatus.Status) 154 assert.True(t, len(nodeStatus.Name) > 0) 155 assert.True(t, nodeStatus.GitHash != "" && nodeStatus.GitHash != "unknown") 156 } 157 158 verboseAssertions := func(t require.TestingT, nodeStatus *models.NodeStatus) { 159 require.NotNil(t, nodeStatus.Stats) 160 assert.Equal(t, int64(2), nodeStatus.Stats.ObjectCount) 161 assert.Equal(t, int64(1), nodeStatus.Stats.ShardCount) 162 assert.Len(t, nodeStatus.Shards, 1) 163 shard := nodeStatus.Shards[0] 164 assert.True(t, len(shard.Name) > 0) 165 assert.Equal(t, docsClass.Class, shard.Class) 166 assert.Equal(t, int64(2), shard.ObjectCount) 167 } 168 169 testStatusResponse(t, minimalAssertions, verboseAssertions, docsClass.Class) 170 }) 171 }) 172 173 // This test prevents a regression of 174 // https://github.com/weaviate/weaviate/issues/2454 175 t.Run("validate count with updates", func(t *testing.T) { 176 booksClass := books.ClassContextionaryVectorizer() 177 helper.CreateClass(t, booksClass) 178 defer helper.DeleteClass(t, booksClass.Class) 179 180 _, err := helper.BatchClient(t).BatchObjectsCreate( 181 batch.NewBatchObjectsCreateParams().WithBody(batch.BatchObjectsCreateBody{ 182 Objects: []*models.Object{ 183 { 184 ID: strfmt.UUID("2D0D3E3B-54B2-48D4-BFE0-4BE2C060110E"), 185 Class: booksClass.Class, 186 Properties: map[string]interface{}{ 187 "title": "A book that changes", 188 "description": "First iteration", 189 }, 190 }, 191 }, 192 }), nil) 193 require.Nil(t, err) 194 195 // Note that this is the same ID as before, so this is an update!! 196 _, err = helper.BatchClient(t).BatchObjectsCreate( 197 batch.NewBatchObjectsCreateParams().WithBody(batch.BatchObjectsCreateBody{ 198 Objects: []*models.Object{ 199 { 200 ID: strfmt.UUID("2D0D3E3B-54B2-48D4-BFE0-4BE2C060110E"), 201 Class: booksClass.Class, 202 Properties: map[string]interface{}{ 203 "title": "A book that changes", 204 "description": "A new (second) iteration", 205 }, 206 }, 207 }, 208 }), nil) 209 require.Nil(t, err) 210 211 minimalAssertions := func(t require.TestingT, nodeStatus *models.NodeStatus) {} 212 verboseAssertions := func(t require.TestingT, nodeStatus *models.NodeStatus) { 213 require.NotNil(t, nodeStatus.Stats) 214 assert.Equal(t, int64(1), nodeStatus.Stats.ObjectCount) 215 } 216 217 testStatusResponse(t, minimalAssertions, verboseAssertions, "") 218 }) 219 } 220 221 func TestNodesApi_Compression_AsyncIndexing(t *testing.T) { 222 ctx := context.Background() 223 compose, err := docker.New(). 224 WithWeaviate(). 225 WithText2VecContextionary(). 226 WithWeaviateEnv("ASYNC_INDEXING", "true"). 227 Start(ctx) 228 require.NoError(t, err) 229 defer func() { 230 require.NoError(t, compose.Terminate(ctx)) 231 }() 232 233 defer helper.SetupClient(fmt.Sprintf("%s:%s", helper.ServerHost, helper.ServerPort)) 234 helper.SetupClient(compose.GetWeaviate().URI()) 235 236 t.Run("validate flat compression status", func(t *testing.T) { 237 booksClass := books.ClassContextionaryVectorizer() 238 booksClass.VectorIndexType = "flat" 239 booksClass.VectorIndexConfig = map[string]interface{}{ 240 "bq": map[string]interface{}{ 241 "enabled": true, 242 }, 243 } 244 helper.CreateClass(t, booksClass) 245 defer helper.DeleteClass(t, booksClass.Class) 246 247 t.Run("check compressed true", func(t *testing.T) { 248 verbose := "verbose" 249 params := nodes.NewNodesGetParams().WithOutput(&verbose) 250 resp, err := helper.Client(t).Nodes.NodesGet(params, nil) 251 require.Nil(t, err) 252 253 nodeStatusResp := resp.GetPayload() 254 require.NotNil(t, nodeStatusResp) 255 256 nodes := nodeStatusResp.Nodes 257 require.NotNil(t, nodes) 258 require.Len(t, nodes, 1) 259 260 nodeStatus := nodes[0] 261 require.NotNil(t, nodeStatus) 262 263 require.True(t, nodeStatus.Shards[0].Compressed) 264 }) 265 }) 266 267 t.Run("validate hnsw pq async compression", func(t *testing.T) { 268 booksClass := books.ClassContextionaryVectorizer() 269 booksClass.VectorIndexConfig = map[string]interface{}{ 270 "pq": map[string]interface{}{ 271 "trainingLimit": 256, 272 "enabled": true, 273 "segments": 1, 274 }, 275 } 276 helper.CreateClass(t, booksClass) 277 defer helper.DeleteClass(t, booksClass.Class) 278 279 t.Run("check compressed initially false", func(t *testing.T) { 280 verbose := "verbose" 281 params := nodes.NewNodesGetParams().WithOutput(&verbose) 282 resp, err := helper.Client(t).Nodes.NodesGet(params, nil) 283 require.Nil(t, err) 284 285 nodeStatusResp := resp.GetPayload() 286 require.NotNil(t, nodeStatusResp) 287 288 nodes := nodeStatusResp.Nodes 289 require.NotNil(t, nodes) 290 require.Len(t, nodes, 1) 291 292 nodeStatus := nodes[0] 293 require.NotNil(t, nodeStatus) 294 295 require.False(t, nodeStatus.Shards[0].Compressed) 296 }) 297 298 t.Run("load data for pq", func(t *testing.T) { 299 num := 1024 300 objects := make([]*models.Object, num) 301 302 for i := 0; i < num; i++ { 303 objects[i] = &models.Object{ 304 Class: booksClass.Class, 305 Vector: []float32{float32(i % 32), float32(i), 3.0, 4.0}, 306 } 307 } 308 309 _, err := helper.BatchClient(t).BatchObjectsCreate( 310 batch.NewBatchObjectsCreateParams().WithBody(batch.BatchObjectsCreateBody{ 311 Objects: objects, 312 }, 313 ), nil) 314 require.Nil(t, err) 315 }) 316 317 t.Run("check eventually compressed if async enabled", func(t *testing.T) { 318 checkThunk := func() interface{} { 319 verbose := "verbose" 320 params := nodes.NewNodesGetParams().WithOutput(&verbose) 321 resp, err := helper.Client(t).Nodes.NodesGet(params, nil) 322 require.Nil(t, err) 323 324 nodeStatusResp := resp.GetPayload() 325 require.NotNil(t, nodeStatusResp) 326 327 nodes := nodeStatusResp.Nodes 328 require.NotNil(t, nodes) 329 require.Len(t, nodes, 1) 330 331 nodeStatus := nodes[0] 332 require.NotNil(t, nodeStatus) 333 return nodeStatus.Shards[0].Compressed 334 } 335 336 helper.AssertEventuallyEqualWithFrequencyAndTimeout(t, true, checkThunk, 100*time.Millisecond, 10*time.Second) 337 }) 338 }) 339 } 340 341 func TestNodesApi_Compression_SyncIndexing(t *testing.T) { 342 t.Run("validate flat compression status", func(t *testing.T) { 343 booksClass := books.ClassContextionaryVectorizer() 344 booksClass.VectorIndexType = "flat" 345 booksClass.VectorIndexConfig = map[string]interface{}{ 346 "bq": map[string]interface{}{ 347 "enabled": true, 348 }, 349 } 350 helper.CreateClass(t, booksClass) 351 defer helper.DeleteClass(t, booksClass.Class) 352 353 t.Run("check compressed true", func(t *testing.T) { 354 verbose := "verbose" 355 params := nodes.NewNodesGetParams().WithOutput(&verbose) 356 resp, err := helper.Client(t).Nodes.NodesGet(params, nil) 357 require.Nil(t, err) 358 359 nodeStatusResp := resp.GetPayload() 360 require.NotNil(t, nodeStatusResp) 361 362 nodes := nodeStatusResp.Nodes 363 require.NotNil(t, nodes) 364 require.Len(t, nodes, 1) 365 366 nodeStatus := nodes[0] 367 require.NotNil(t, nodeStatus) 368 369 require.True(t, nodeStatus.Shards[0].Compressed) 370 }) 371 }) 372 } 373 374 func testStatusResponse(t *testing.T, minimalAssertions, verboseAssertions func(require.TestingT, *models.NodeStatus), 375 class string, 376 ) { 377 minimal, verbose := verbosity.OutputMinimal, verbosity.OutputVerbose 378 379 commonTests := func(resp *nodes.NodesGetOK) { 380 require.NotNil(t, resp.Payload) 381 nodes := resp.Payload.Nodes 382 require.NotNil(t, nodes) 383 require.Len(t, nodes, 1) 384 minimalAssertions(t, nodes[0]) 385 } 386 387 t.Run("minimal", func(t *testing.T) { 388 payload, err := getNodesStatus(t, minimal, class) 389 require.Nil(t, err) 390 commonTests(&nodes.NodesGetOK{Payload: payload}) 391 }) 392 393 if verboseAssertions != nil { 394 t.Run("verbose", func(t *testing.T) { 395 getNodes := func() (*models.NodesStatusResponse, error) { 396 return getNodesStatus(t, verbose, class) 397 } 398 assert.EventuallyWithT(t, func(t *assert.CollectT) { 399 payload, err := getNodes() 400 require.Nil(t, err) 401 commonTests(&nodes.NodesGetOK{Payload: payload}) 402 // If commonTests pass, resp.Nodes[0] != nil 403 verboseAssertions(t, payload.Nodes[0]) 404 }, 15*time.Second, 500*time.Millisecond) 405 }) 406 } 407 } 408 409 func getNodesStatus(t *testing.T, output, class string) (payload *models.NodesStatusResponse, err error) { 410 if class != "" { 411 params := nodes.NewNodesGetClassParams().WithOutput(&output).WithClassName(class) 412 body, clientErr := helper.Client(t).Nodes.NodesGetClass(params, nil) 413 payload, err = body.Payload, clientErr 414 } else { 415 params := nodes.NewNodesGetParams().WithOutput(&output) 416 body, clientErr := helper.Client(t).Nodes.NodesGet(params, nil) 417 payload, err = body.Payload, clientErr 418 } 419 return 420 }