github.com/weaviate/weaviate@v1.24.6/usecases/schema/commit_failure_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 schema
    13  
    14  import (
    15  	"context"
    16  	"encoding/json"
    17  	"fmt"
    18  	"testing"
    19  
    20  	"github.com/stretchr/testify/assert"
    21  	"github.com/stretchr/testify/require"
    22  	"github.com/weaviate/weaviate/entities/models"
    23  	"github.com/weaviate/weaviate/entities/schema"
    24  	"github.com/weaviate/weaviate/usecases/sharding"
    25  )
    26  
    27  // This test makes sure that even when a commit fails, the coordinator still
    28  // honors the commit. This was introduced as part of
    29  // https://github.com/weaviate/weaviate/issues/2616, where a schema
    30  // inconsistency was guaranteed as soon as any node died in it's commit phase
    31  // because the coordinator would behave differently than other (alive) which it
    32  // told to commit.
    33  func TestFailedCommits(t *testing.T) {
    34  	type test struct {
    35  		name string
    36  		// prepare runs before any commit errors occur to build an initial state
    37  		prepare func(*testing.T, *Manager)
    38  		// action runs with commit errors
    39  		action    func(*testing.T, *Manager)
    40  		expSchema []*models.Class
    41  	}
    42  
    43  	ctx := context.Background()
    44  	vTrue := true
    45  	vFalse := false
    46  
    47  	tests := []test{
    48  		{
    49  			name: "Add a class",
    50  			action: func(t *testing.T, sm *Manager) {
    51  				sm.AddClass(ctx, nil, &models.Class{
    52  					Class:           "MyClass",
    53  					VectorIndexType: "hnsw",
    54  				})
    55  			},
    56  			expSchema: []*models.Class{
    57  				classWithDefaultsWithProps(t, "MyClass", nil),
    58  			},
    59  		},
    60  		{
    61  			name: "Delete a class",
    62  			prepare: func(t *testing.T, sm *Manager) {
    63  				sm.AddClass(ctx, nil, &models.Class{
    64  					Class:           "MyClass",
    65  					VectorIndexType: "hnsw",
    66  				})
    67  				sm.AddClass(ctx, nil, &models.Class{
    68  					Class:           "OtherClass",
    69  					VectorIndexType: "hnsw",
    70  				})
    71  			},
    72  			action: func(t *testing.T, sm *Manager) {
    73  				assert.Nil(t, sm.DeleteClass(ctx, nil, "MyClass"))
    74  			},
    75  			expSchema: []*models.Class{
    76  				classWithDefaultsWithProps(t, "OtherClass", nil),
    77  			},
    78  		},
    79  		{
    80  			name: "Extend a class with a property",
    81  			prepare: func(t *testing.T, sm *Manager) {
    82  				sm.AddClass(ctx, nil, &models.Class{
    83  					Class:           "MyClass",
    84  					VectorIndexType: "hnsw",
    85  				})
    86  			},
    87  			action: func(t *testing.T, sm *Manager) {
    88  				err := sm.AddClassProperty(ctx, nil, "MyClass", &models.Property{
    89  					Name:     "prop_1",
    90  					DataType: schema.DataTypeInt.PropString(),
    91  				})
    92  				assert.Nil(t, err)
    93  			},
    94  			expSchema: []*models.Class{
    95  				classWithDefaultsWithProps(t, "MyClass", []*models.Property{
    96  					{
    97  						Name:            "prop_1",
    98  						DataType:        schema.DataTypeInt.PropString(),
    99  						IndexFilterable: &vTrue,
   100  						IndexSearchable: &vFalse,
   101  					},
   102  				}),
   103  			},
   104  		},
   105  	}
   106  
   107  	for _, test := range tests {
   108  		t.Run(test.name, func(t *testing.T) {
   109  			clusterState := &fakeClusterState{
   110  				hosts: []string{"node1", "node2"},
   111  			}
   112  
   113  			// required for the startup sync
   114  			txJSON, _ := json.Marshal(ReadSchemaPayload{
   115  				Schema: &State{
   116  					ObjectSchema: &models.Schema{
   117  						Classes: []*models.Class{},
   118  					},
   119  				},
   120  			})
   121  
   122  			txClient := &fakeTxClient{
   123  				openInjectPayload: json.RawMessage(txJSON), // required for the startup sync
   124  			}
   125  
   126  			initialSchema := &State{
   127  				ObjectSchema:  &models.Schema{},
   128  				ShardingState: map[string]*sharding.State{},
   129  			}
   130  
   131  			sm, err := newManagerWithClusterAndTx(t, clusterState, txClient, initialSchema)
   132  			require.Nil(t, err)
   133  
   134  			sm.StartServing(context.Background())
   135  
   136  			if test.prepare != nil {
   137  				test.prepare(t, sm)
   138  			}
   139  
   140  			txClient.commitErr = fmt.Errorf("Oh I, I just died in your arms tonight")
   141  			test.action(t, sm)
   142  
   143  			assert.ElementsMatch(t, test.expSchema, sm.GetSchemaSkipAuth().Objects.Classes)
   144  		})
   145  	}
   146  }
   147  
   148  func classWithDefaultsWithProps(t *testing.T, name string,
   149  	props []*models.Property,
   150  ) *models.Class {
   151  	class := &models.Class{Class: name, VectorIndexType: "hnsw"}
   152  	class.Vectorizer = "none"
   153  
   154  	sc, err := sharding.ParseConfig(map[string]interface{}{}, 1)
   155  	require.Nil(t, err)
   156  
   157  	class.ShardingConfig = sc
   158  
   159  	class.VectorIndexConfig = fakeVectorConfig{}
   160  	class.ReplicationConfig = &models.ReplicationConfig{Factor: 1}
   161  	class.MultiTenancyConfig = &models.MultiTenancyConfig{Enabled: false}
   162  
   163  	(&fakeModuleConfig{}).SetClassDefaults(class)
   164  	setInvertedConfigDefaults(class)
   165  
   166  	class.Properties = props
   167  
   168  	return class
   169  }