KevinStephenson
Adding in weaviate code
b110593
raw
history blame
14 kB
// _ _
// __ _____ __ ___ ___ __ _| |_ ___
// \ \ /\ / / _ \/ _` \ \ / / |/ _` | __/ _ \
// \ V V / __/ (_| |\ V /| | (_| | || __/
// \_/\_/ \___|\__,_| \_/ |_|\__,_|\__\___|
//
// Copyright © 2016 - 2024 Weaviate B.V. All rights reserved.
//
// CONTACT: [email protected]
//
package test
import (
"context"
"fmt"
"testing"
"time"
"github.com/go-openapi/strfmt"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/weaviate/weaviate/client/batch"
"github.com/weaviate/weaviate/client/meta"
"github.com/weaviate/weaviate/client/nodes"
"github.com/weaviate/weaviate/entities/models"
"github.com/weaviate/weaviate/entities/verbosity"
"github.com/weaviate/weaviate/test/docker"
"github.com/weaviate/weaviate/test/helper"
"github.com/weaviate/weaviate/test/helper/sample-schema/books"
"github.com/weaviate/weaviate/test/helper/sample-schema/documents"
"github.com/weaviate/weaviate/test/helper/sample-schema/multishard"
)
func Test_NodesAPI(t *testing.T) {
t.Run("empty DB", func(t *testing.T) {
meta, err := helper.Client(t).Meta.MetaGet(meta.NewMetaGetParams(), nil)
require.Nil(t, err)
assert.NotNil(t, meta.GetPayload())
assertions := func(t *testing.T, nodeStatus *models.NodeStatus) {
require.NotNil(t, nodeStatus)
assert.Equal(t, models.NodeStatusStatusHEALTHY, *nodeStatus.Status)
assert.True(t, len(nodeStatus.Name) > 0)
assert.True(t, nodeStatus.GitHash != "" && nodeStatus.GitHash != "unknown")
assert.Equal(t, meta.Payload.Version, nodeStatus.Version)
assert.Empty(t, nodeStatus.Shards)
require.NotNil(t, nodeStatus.Stats)
assert.Equal(t, int64(0), nodeStatus.Stats.ObjectCount)
assert.Equal(t, int64(0), nodeStatus.Stats.ShardCount)
}
testStatusResponse(t, assertions, nil, "")
})
t.Run("DB with Books (1 class ,1 shard configuration, 1 node)", func(t *testing.T) {
booksClass := books.ClassContextionaryVectorizer()
helper.CreateClass(t, booksClass)
defer helper.DeleteClass(t, booksClass.Class)
for _, book := range books.Objects() {
helper.CreateObject(t, book)
helper.AssertGetObjectEventually(t, book.Class, book.ID)
}
minimalAssertions := func(t *testing.T, nodeStatus *models.NodeStatus) {
require.NotNil(t, nodeStatus)
assert.Equal(t, models.NodeStatusStatusHEALTHY, *nodeStatus.Status)
assert.True(t, len(nodeStatus.Name) > 0)
assert.True(t, nodeStatus.GitHash != "" && nodeStatus.GitHash != "unknown")
require.NotNil(t, nodeStatus.Stats)
assert.Equal(t, int64(3), nodeStatus.Stats.ObjectCount)
assert.Equal(t, int64(1), nodeStatus.Stats.ShardCount)
}
verboseAssertions := func(t *testing.T, nodeStatus *models.NodeStatus) {
require.Len(t, nodeStatus.Shards, 1)
shard := nodeStatus.Shards[0]
assert.True(t, len(shard.Name) > 0)
assert.Equal(t, booksClass.Class, shard.Class)
assert.Equal(t, int64(3), shard.ObjectCount)
}
testStatusResponse(t, minimalAssertions, verboseAssertions, "")
})
t.Run("DB with MultiShard (1 class, 2 shards configuration, 1 node)", func(t *testing.T) {
multiShardClass := multishard.ClassContextionaryVectorizer()
helper.CreateClass(t, multiShardClass)
defer helper.DeleteClass(t, multiShardClass.Class)
for _, multiShard := range multishard.Objects() {
helper.CreateObject(t, multiShard)
helper.AssertGetObjectEventually(t, multiShard.Class, multiShard.ID)
}
minimalAssertions := func(t *testing.T, nodeStatus *models.NodeStatus) {
require.NotNil(t, nodeStatus)
assert.Equal(t, models.NodeStatusStatusHEALTHY, *nodeStatus.Status)
assert.True(t, len(nodeStatus.Name) > 0)
assert.True(t, nodeStatus.GitHash != "" && nodeStatus.GitHash != "unknown")
require.NotNil(t, nodeStatus.Stats)
assert.Equal(t, int64(3), nodeStatus.Stats.ObjectCount)
assert.Equal(t, int64(2), nodeStatus.Stats.ShardCount)
}
verboseAsssertions := func(t *testing.T, nodeStatus *models.NodeStatus) {
assert.Len(t, nodeStatus.Shards, 2)
for _, shard := range nodeStatus.Shards {
assert.True(t, len(shard.Name) > 0)
assert.Equal(t, multiShardClass.Class, shard.Class)
assert.GreaterOrEqual(t, shard.ObjectCount, int64(0))
}
}
testStatusResponse(t, minimalAssertions, verboseAsssertions, "")
})
t.Run("with class name: DB with Books and Documents, 1 shard, 1 node", func(t *testing.T) {
booksClass := books.ClassContextionaryVectorizer()
helper.CreateClass(t, booksClass)
defer helper.DeleteClass(t, booksClass.Class)
t.Run("insert and check books", func(t *testing.T) {
for _, book := range books.Objects() {
helper.CreateObject(t, book)
helper.AssertGetObjectEventually(t, book.Class, book.ID)
}
assertions := func(t *testing.T, nodeStatus *models.NodeStatus) {
require.NotNil(t, nodeStatus.Stats)
assert.Equal(t, int64(3), nodeStatus.Stats.ObjectCount)
assert.Equal(t, int64(1), nodeStatus.Stats.ShardCount)
}
testStatusResponse(t, assertions, nil, "")
})
t.Run("insert and check documents", func(t *testing.T) {
docsClasses := documents.ClassesContextionaryVectorizer(false)
helper.CreateClass(t, docsClasses[0])
helper.CreateClass(t, docsClasses[1])
defer helper.DeleteClass(t, docsClasses[0].Class)
defer helper.DeleteClass(t, docsClasses[1].Class)
for _, doc := range documents.Objects() {
helper.CreateObject(t, doc)
helper.AssertGetObjectEventually(t, doc.Class, doc.ID)
}
docsClass := docsClasses[0]
minimalAssertions := func(t *testing.T, nodeStatus *models.NodeStatus) {
assert.Equal(t, models.NodeStatusStatusHEALTHY, *nodeStatus.Status)
assert.True(t, len(nodeStatus.Name) > 0)
assert.True(t, nodeStatus.GitHash != "" && nodeStatus.GitHash != "unknown")
require.NotNil(t, nodeStatus.Stats)
assert.Equal(t, int64(2), nodeStatus.Stats.ObjectCount)
assert.Equal(t, int64(1), nodeStatus.Stats.ShardCount)
}
verboseAssertions := func(t *testing.T, nodeStatus *models.NodeStatus) {
assert.Len(t, nodeStatus.Shards, 1)
shard := nodeStatus.Shards[0]
assert.True(t, len(shard.Name) > 0)
assert.Equal(t, docsClass.Class, shard.Class)
assert.Equal(t, int64(2), shard.ObjectCount)
}
testStatusResponse(t, minimalAssertions, verboseAssertions, docsClass.Class)
})
})
// This test prevents a regression of
// https://github.com/weaviate/weaviate/issues/2454
t.Run("validate count with updates", func(t *testing.T) {
booksClass := books.ClassContextionaryVectorizer()
helper.CreateClass(t, booksClass)
defer helper.DeleteClass(t, booksClass.Class)
_, err := helper.BatchClient(t).BatchObjectsCreate(
batch.NewBatchObjectsCreateParams().WithBody(batch.BatchObjectsCreateBody{
Objects: []*models.Object{
{
ID: strfmt.UUID("2D0D3E3B-54B2-48D4-BFE0-4BE2C060110E"),
Class: booksClass.Class,
Properties: map[string]interface{}{
"title": "A book that changes",
"description": "First iteration",
},
},
},
}), nil)
require.Nil(t, err)
// Note that this is the same ID as before, so this is an update!!
_, err = helper.BatchClient(t).BatchObjectsCreate(
batch.NewBatchObjectsCreateParams().WithBody(batch.BatchObjectsCreateBody{
Objects: []*models.Object{
{
ID: strfmt.UUID("2D0D3E3B-54B2-48D4-BFE0-4BE2C060110E"),
Class: booksClass.Class,
Properties: map[string]interface{}{
"title": "A book that changes",
"description": "A new (second) iteration",
},
},
},
}), nil)
require.Nil(t, err)
assertions := func(t *testing.T, nodeStatus *models.NodeStatus) {
require.NotNil(t, nodeStatus.Stats)
assert.Equal(t, int64(1), nodeStatus.Stats.ObjectCount)
}
testStatusResponse(t, assertions, nil, "")
})
}
func TestNodesApi_Compression_AsyncIndexing(t *testing.T) {
ctx := context.Background()
compose, err := docker.New().
WithWeaviate().
WithText2VecContextionary().
WithWeaviateEnv("ASYNC_INDEXING", "true").
Start(ctx)
require.NoError(t, err)
defer func() {
require.NoError(t, compose.Terminate(ctx))
}()
defer helper.SetupClient(fmt.Sprintf("%s:%s", helper.ServerHost, helper.ServerPort))
helper.SetupClient(compose.GetWeaviate().URI())
t.Run("validate flat compression status", func(t *testing.T) {
booksClass := books.ClassContextionaryVectorizer()
booksClass.VectorIndexType = "flat"
booksClass.VectorIndexConfig = map[string]interface{}{
"bq": map[string]interface{}{
"enabled": true,
},
}
helper.CreateClass(t, booksClass)
defer helper.DeleteClass(t, booksClass.Class)
t.Run("check compressed true", func(t *testing.T) {
verbose := "verbose"
params := nodes.NewNodesGetParams().WithOutput(&verbose)
resp, err := helper.Client(t).Nodes.NodesGet(params, nil)
require.Nil(t, err)
nodeStatusResp := resp.GetPayload()
require.NotNil(t, nodeStatusResp)
nodes := nodeStatusResp.Nodes
require.NotNil(t, nodes)
require.Len(t, nodes, 1)
nodeStatus := nodes[0]
require.NotNil(t, nodeStatus)
require.True(t, nodeStatus.Shards[0].Compressed)
})
})
t.Run("validate hnsw pq async compression", func(t *testing.T) {
booksClass := books.ClassContextionaryVectorizer()
booksClass.VectorIndexConfig = map[string]interface{}{
"pq": map[string]interface{}{
"trainingLimit": 256,
"enabled": true,
"segments": 1,
},
}
helper.CreateClass(t, booksClass)
defer helper.DeleteClass(t, booksClass.Class)
t.Run("check compressed initially false", func(t *testing.T) {
verbose := "verbose"
params := nodes.NewNodesGetParams().WithOutput(&verbose)
resp, err := helper.Client(t).Nodes.NodesGet(params, nil)
require.Nil(t, err)
nodeStatusResp := resp.GetPayload()
require.NotNil(t, nodeStatusResp)
nodes := nodeStatusResp.Nodes
require.NotNil(t, nodes)
require.Len(t, nodes, 1)
nodeStatus := nodes[0]
require.NotNil(t, nodeStatus)
require.False(t, nodeStatus.Shards[0].Compressed)
})
t.Run("load data for pq", func(t *testing.T) {
num := 1024
objects := make([]*models.Object, num)
for i := 0; i < num; i++ {
objects[i] = &models.Object{
Class: booksClass.Class,
Vector: []float32{float32(i % 32), float32(i), 3.0, 4.0},
}
}
_, err := helper.BatchClient(t).BatchObjectsCreate(
batch.NewBatchObjectsCreateParams().WithBody(batch.BatchObjectsCreateBody{
Objects: objects,
},
), nil)
require.Nil(t, err)
})
t.Run("check eventually compressed if async enabled", func(t *testing.T) {
checkThunk := func() interface{} {
verbose := "verbose"
params := nodes.NewNodesGetParams().WithOutput(&verbose)
resp, err := helper.Client(t).Nodes.NodesGet(params, nil)
require.Nil(t, err)
nodeStatusResp := resp.GetPayload()
require.NotNil(t, nodeStatusResp)
nodes := nodeStatusResp.Nodes
require.NotNil(t, nodes)
require.Len(t, nodes, 1)
nodeStatus := nodes[0]
require.NotNil(t, nodeStatus)
return nodeStatus.Shards[0].Compressed
}
helper.AssertEventuallyEqualWithFrequencyAndTimeout(t, true, checkThunk, 100*time.Millisecond, 10*time.Second)
})
})
}
func TestNodesApi_Compression_SyncIndexing(t *testing.T) {
t.Run("validate flat compression status", func(t *testing.T) {
booksClass := books.ClassContextionaryVectorizer()
booksClass.VectorIndexType = "flat"
booksClass.VectorIndexConfig = map[string]interface{}{
"bq": map[string]interface{}{
"enabled": true,
},
}
helper.CreateClass(t, booksClass)
defer helper.DeleteClass(t, booksClass.Class)
t.Run("check compressed true", func(t *testing.T) {
verbose := "verbose"
params := nodes.NewNodesGetParams().WithOutput(&verbose)
resp, err := helper.Client(t).Nodes.NodesGet(params, nil)
require.Nil(t, err)
nodeStatusResp := resp.GetPayload()
require.NotNil(t, nodeStatusResp)
nodes := nodeStatusResp.Nodes
require.NotNil(t, nodes)
require.Len(t, nodes, 1)
nodeStatus := nodes[0]
require.NotNil(t, nodeStatus)
require.True(t, nodeStatus.Shards[0].Compressed)
})
})
}
func testStatusResponse(t *testing.T, minimalAssertions, verboseAssertions func(*testing.T, *models.NodeStatus),
class string,
) {
minimal, verbose := verbosity.OutputMinimal, verbosity.OutputVerbose
commonTests := func(resp *nodes.NodesGetOK) {
require.NotNil(t, resp.Payload)
nodes := resp.Payload.Nodes
require.NotNil(t, nodes)
require.Len(t, nodes, 1)
minimalAssertions(t, nodes[0])
}
t.Run("minimal", func(t *testing.T) {
payload, err := getNodesStatus(t, minimal, class)
require.Nil(t, err)
commonTests(&nodes.NodesGetOK{Payload: payload})
})
if verboseAssertions != nil {
t.Run("verbose", func(t *testing.T) {
payload, err := getNodesStatus(t, verbose, class)
require.Nil(t, err)
commonTests(&nodes.NodesGetOK{Payload: payload})
// If commonTests pass, resp.Nodes[0] != nil
verboseAssertions(t, payload.Nodes[0])
})
}
}
func getNodesStatus(t *testing.T, output, class string) (payload *models.NodesStatusResponse, err error) {
if class != "" {
params := nodes.NewNodesGetClassParams().WithOutput(&output).WithClassName(class)
body, clientErr := helper.Client(t).Nodes.NodesGetClass(params, nil)
payload, err = body.Payload, clientErr
} else {
params := nodes.NewNodesGetParams().WithOutput(&output)
body, clientErr := helper.Client(t).Nodes.NodesGet(params, nil)
payload, err = body.Payload, clientErr
}
return
}