github.com/weaviate/weaviate@v1.24.6/test/acceptance/multi_tenancy/batch_add_tenant_references_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 "fmt" 16 "testing" 17 18 "github.com/google/uuid" 19 20 "github.com/go-openapi/strfmt" 21 "github.com/stretchr/testify/assert" 22 "github.com/stretchr/testify/require" 23 "github.com/weaviate/weaviate/entities/models" 24 "github.com/weaviate/weaviate/entities/schema" 25 "github.com/weaviate/weaviate/entities/schema/crossref" 26 "github.com/weaviate/weaviate/test/helper" 27 ) 28 29 func TestBatchAddTenantReferences(t *testing.T) { 30 className1 := "MultiTenantClass1" 31 className2 := "MultiTenantClass2" 32 className3 := "SingleTenantClass1" 33 className4 := "SingleTenantClass2" 34 tenantName1 := "Tenant1" 35 tenantName2 := "Tenant2" 36 mtRefProp1 := "relatedToMT1" 37 mtRefProp2 := "relatedToMT2" 38 stRefProp := "relatedToST" 39 mtClass1 := models.Class{ 40 Class: className1, 41 MultiTenancyConfig: &models.MultiTenancyConfig{Enabled: true}, 42 Properties: []*models.Property{ 43 { 44 Name: "name", 45 DataType: schema.DataTypeText.PropString(), 46 }, 47 { 48 Name: mtRefProp1, 49 DataType: []string{className1}, 50 }, 51 { 52 Name: mtRefProp2, 53 DataType: []string{className2}, 54 }, 55 { 56 Name: stRefProp, 57 DataType: []string{className3}, 58 }, 59 }, 60 } 61 mtClass2 := models.Class{ 62 Class: className2, 63 MultiTenancyConfig: &models.MultiTenancyConfig{ 64 Enabled: true, 65 }, 66 Properties: []*models.Property{ 67 { 68 Name: "name", 69 DataType: schema.DataTypeText.PropString(), 70 }, 71 }, 72 } 73 stClass1 := models.Class{ 74 Class: className3, 75 Properties: []*models.Property{ 76 { 77 Name: "stringProp", 78 DataType: schema.DataTypeText.PropString(), 79 }, 80 }, 81 } 82 stClass2 := models.Class{ 83 Class: className4, 84 Properties: []*models.Property{ 85 { 86 Name: mtRefProp1, 87 DataType: []string{className1}, 88 }, 89 }, 90 } 91 mtObject1 := &models.Object{ 92 ID: "0927a1e0-398e-4e76-91fb-04a7a8f0405c", 93 Class: className1, 94 Properties: map[string]interface{}{ 95 "name": tenantName1, 96 }, 97 Tenant: tenantName1, 98 } 99 mtObject2DiffTenant := &models.Object{ 100 ID: "af90a7e3-53b3-4eb0-b395-10a04d217263", 101 Class: className2, 102 Properties: map[string]interface{}{ 103 "name": tenantName2, 104 }, 105 Tenant: tenantName2, 106 } 107 mtObject2SameTenant := &models.Object{ 108 ID: "4076df6b-0767-43a9-a0a4-2ec153bf262e", 109 Class: className2, 110 Properties: map[string]interface{}{ 111 "name": tenantName1, 112 }, 113 Tenant: tenantName1, 114 } 115 stObject1 := &models.Object{ 116 ID: "bea841c7-d689-4526-8af3-56c44b44274a", 117 Class: className3, 118 Properties: map[string]interface{}{ 119 "stringProp": "123", 120 }, 121 } 122 stObject2 := &models.Object{ 123 ID: "744f869a-7dcb-4fb5-8b0a-73075da1e116", 124 Class: className4, 125 } 126 127 defer func() { 128 helper.DeleteClass(t, className1) 129 helper.DeleteClass(t, className2) 130 helper.DeleteClass(t, className3) 131 helper.DeleteClass(t, className4) 132 }() 133 134 t.Run("create classes", func(t *testing.T) { 135 helper.CreateClass(t, &stClass1) 136 helper.CreateClass(t, &mtClass2) 137 helper.CreateClass(t, &mtClass1) 138 helper.CreateClass(t, &stClass2) 139 }) 140 141 t.Run("create tenants", func(t *testing.T) { 142 helper.CreateTenants(t, className1, []*models.Tenant{{Name: tenantName1}}) 143 helper.CreateTenants(t, className2, []*models.Tenant{{Name: tenantName1}}) 144 helper.CreateTenants(t, className2, []*models.Tenant{{Name: tenantName2}}) 145 }) 146 147 t.Run("add tenant objects", func(t *testing.T) { 148 helper.CreateObject(t, mtObject1) 149 helper.CreateObject(t, mtObject2DiffTenant) 150 helper.CreateObject(t, mtObject2SameTenant) 151 helper.CreateObject(t, stObject1) 152 helper.CreateObject(t, stObject2) 153 154 t.Run("verify objects creation", func(t *testing.T) { 155 resp, err := helper.TenantObject(t, mtObject1.Class, mtObject1.ID, tenantName1) 156 require.Nil(t, err) 157 require.Equal(t, mtObject1.Class, resp.Class) 158 require.Equal(t, mtObject1.Properties, resp.Properties) 159 160 resp, err = helper.TenantObject(t, mtObject2DiffTenant.Class, mtObject2DiffTenant.ID, tenantName2) 161 require.Nil(t, err) 162 require.Equal(t, mtObject2DiffTenant.Class, resp.Class) 163 require.Equal(t, mtObject2DiffTenant.Properties, resp.Properties) 164 165 resp, err = helper.TenantObject(t, mtObject2SameTenant.Class, mtObject2SameTenant.ID, tenantName1) 166 require.Nil(t, err) 167 require.Equal(t, mtObject2SameTenant.Class, resp.Class) 168 require.Equal(t, mtObject2SameTenant.Properties, resp.Properties) 169 170 resp, err = helper.GetObject(t, stObject1.Class, stObject1.ID) 171 require.Nil(t, err) 172 require.Equal(t, stObject1.Class, resp.Class) 173 174 resp, err = helper.GetObject(t, stObject2.Class, stObject2.ID) 175 require.Nil(t, err) 176 require.Equal(t, stObject2.Class, resp.Class) 177 }) 178 }) 179 180 t.Run("add tenant reference - same class and tenant", func(t *testing.T) { 181 refs := []*models.BatchReference{ 182 { 183 From: strfmt.URI(crossref.NewSource(schema.ClassName(className1), 184 schema.PropertyName(mtRefProp1), mtObject1.ID).String()), 185 To: strfmt.URI(crossref.NewLocalhost(className1, mtObject1.ID).String()), 186 Tenant: tenantName1, 187 }, 188 } 189 resp, err := helper.AddReferences(t, refs) 190 helper.CheckReferencesBatchResponse(t, resp, err) 191 192 t.Run("verify object references", func(t *testing.T) { 193 resp, err := helper.TenantObject(t, mtObject1.Class, mtObject1.ID, tenantName1) 194 require.Nil(t, err) 195 require.Equal(t, mtObject1.Class, resp.Class) 196 require.Equal(t, mtObject1.ID, resp.ID) 197 relatedTo := resp.Properties.(map[string]interface{})[mtRefProp1].([]interface{}) 198 require.Len(t, relatedTo, 1) 199 beacon := relatedTo[0].(map[string]interface{})["beacon"].(string) 200 assert.Equal(t, helper.NewBeacon(className1, mtObject1.ID), strfmt.URI(beacon)) 201 }) 202 }) 203 204 t.Run("add tenant reference - different MT class same tenant", func(t *testing.T) { 205 refs := []*models.BatchReference{ 206 { 207 From: strfmt.URI(crossref.NewSource(schema.ClassName(className1), 208 schema.PropertyName(mtRefProp2), mtObject1.ID).String()), 209 To: strfmt.URI(crossref.NewLocalhost(className2, mtObject2SameTenant.ID).String()), 210 Tenant: tenantName1, 211 }, 212 } 213 resp, err := helper.AddReferences(t, refs) 214 helper.CheckReferencesBatchResponse(t, resp, err) 215 216 t.Run("verify object references", func(t *testing.T) { 217 resp, err := helper.TenantObject(t, mtObject1.Class, mtObject1.ID, tenantName1) 218 require.Nil(t, err) 219 require.Equal(t, mtObject1.Class, resp.Class) 220 require.Equal(t, mtObject1.ID, resp.ID) 221 relatedTo := resp.Properties.(map[string]interface{})[mtRefProp2].([]interface{}) 222 require.Len(t, relatedTo, 1) 223 beacon := relatedTo[0].(map[string]interface{})["beacon"].(string) 224 assert.Equal(t, helper.NewBeacon(className2, mtObject2SameTenant.ID), strfmt.URI(beacon)) 225 }) 226 }) 227 228 t.Run("no references between different tenants", func(t *testing.T) { 229 refs := []*models.BatchReference{ 230 { 231 From: strfmt.URI(crossref.NewSource(schema.ClassName(className1), 232 schema.PropertyName(mtRefProp2), mtObject1.ID).String()), 233 To: strfmt.URI(crossref.NewLocalhost(className2, mtObject2DiffTenant.ID).String()), 234 Tenant: tenantName1, 235 }, 236 } 237 238 resp, err := helper.AddReferences(t, refs) 239 require.Nil(t, err) 240 require.NotNil(t, resp) 241 require.Len(t, resp, 1) 242 require.Empty(t, resp[0].To) 243 require.Empty(t, resp[0].From) 244 require.NotNil(t, resp[0].Result) 245 require.NotNil(t, resp[0].Result.Errors) 246 require.Len(t, resp[0].Result.Errors.Error, 1) 247 require.NotNil(t, resp[0].Result.Errors.Error[0]) 248 expectedMsg := fmt.Sprintf(`target: object %s/%s not found for tenant %q`, className2, mtObject2DiffTenant.ID, tenantName1) 249 assert.Equal(t, expectedMsg, resp[0].Result.Errors.Error[0].Message) 250 }) 251 252 t.Run("add tenant reference - from MT class to single tenant class", func(t *testing.T) { 253 refs := []*models.BatchReference{ 254 { 255 From: strfmt.URI(crossref.NewSource(schema.ClassName(className1), 256 schema.PropertyName(stRefProp), mtObject1.ID).String()), 257 To: strfmt.URI(crossref.NewLocalhost(className3, stObject1.ID).String()), 258 Tenant: tenantName1, 259 }, 260 } 261 resp, err := helper.AddReferences(t, refs) 262 helper.CheckReferencesBatchResponse(t, resp, err) 263 264 t.Run("verify object references", func(t *testing.T) { 265 resp, err := helper.TenantObject(t, mtObject1.Class, mtObject1.ID, tenantName1) 266 require.Nil(t, err) 267 require.Equal(t, mtObject1.Class, resp.Class) 268 require.Equal(t, mtObject1.ID, resp.ID) 269 relatedTo := resp.Properties.(map[string]interface{})[stRefProp].([]interface{}) 270 require.Len(t, relatedTo, 1) 271 beacon := relatedTo[0].(map[string]interface{})["beacon"].(string) 272 assert.Equal(t, helper.NewBeacon(className3, stObject1.ID), strfmt.URI(beacon)) 273 }) 274 }) 275 276 t.Run("no references from single tenant class to MT class", func(t *testing.T) { 277 refs := []*models.BatchReference{ 278 { 279 From: strfmt.URI(crossref.NewSource(schema.ClassName(className4), 280 schema.PropertyName(mtRefProp1), stObject2.ID).String()), 281 To: strfmt.URI(crossref.NewLocalhost(className1, mtObject1.ID).String()), 282 }, 283 } 284 285 resp, err := helper.AddReferences(t, refs) 286 require.Nil(t, err) 287 require.NotNil(t, resp) 288 require.Len(t, resp, 1) 289 require.Empty(t, resp[0].To) 290 require.Empty(t, resp[0].From) 291 require.NotNil(t, resp[0].Result) 292 require.NotNil(t, resp[0].Result.Errors) 293 require.Len(t, resp[0].Result.Errors.Error, 1) 294 require.NotNil(t, resp[0].Result.Errors.Error[0]) 295 expectedMsg := "invalid reference: cannot reference a multi-tenant enabled class from a non multi-tenant enabled class" 296 assert.Equal(t, expectedMsg, resp[0].Result.Errors.Error[0].Message) 297 }) 298 } 299 300 func TestAddMultipleTenantsForBatch(t *testing.T) { 301 tenants := []string{"tenant1", "tenant2"} 302 classNames := []string{"MultiTenantRefs1", "MultiTenantRefs2", "MultiTenantRefs3"} 303 refProps := []string{"refPropST", "refPropOtherMT", "refPropSelf"} 304 classes := []models.Class{ 305 {Class: classNames[0]}, 306 { 307 Class: classNames[1], 308 MultiTenancyConfig: &models.MultiTenancyConfig{Enabled: true}, 309 }, 310 { 311 Class: classNames[2], 312 MultiTenancyConfig: &models.MultiTenancyConfig{Enabled: true}, 313 Properties: []*models.Property{ 314 { 315 Name: refProps[0], 316 DataType: []string{classNames[0]}, 317 }, 318 { 319 Name: refProps[1], 320 DataType: []string{classNames[1]}, 321 }, 322 { 323 Name: refProps[2], 324 DataType: []string{classNames[2]}, 325 }, 326 }, 327 }, 328 } 329 defer func() { 330 for i := range classes { 331 helper.DeleteClass(t, classes[i].Class) 332 } 333 }() 334 for i := range classes { 335 helper.CreateClass(t, &classes[i]) 336 } 337 338 for _, class := range classes[1:] { 339 for k := range tenants { 340 helper.CreateTenants(t, class.Class, []*models.Tenant{{Name: tenants[k]}}) 341 } 342 } 343 344 var tenantObjects []*models.Object 345 objMap := make(map[string][]int) 346 347 for i := 0; i < 9; i++ { 348 obj := &models.Object{ 349 ID: strfmt.UUID(uuid.New().String()), 350 Class: classes[i%len(classes)].Class, 351 } 352 if i%len(classes) > 0 { // only for MMT class 353 obj.Tenant = tenants[i%len(tenants)] 354 } 355 tenantObjects = append(tenantObjects, obj) 356 objMap[obj.Class] = append(objMap[obj.Class], i) 357 } 358 helper.CreateObjectsBatch(t, tenantObjects) 359 360 t.Run("refs between same class", func(t *testing.T) { 361 var refs []*models.BatchReference 362 for _, objectIndex := range objMap[classNames[2]] { 363 obj := tenantObjects[objectIndex] 364 refs = append(refs, &models.BatchReference{ 365 From: strfmt.URI(crossref.NewSource(schema.ClassName(obj.Class), 366 schema.PropertyName(refProps[2]), obj.ID).String()), 367 To: strfmt.URI(crossref.NewLocalhost(classNames[2], obj.ID).String()), 368 Tenant: obj.Tenant, 369 }, 370 ) 371 } 372 resp, err := helper.AddReferences(t, refs) 373 helper.CheckReferencesBatchResponse(t, resp, err) 374 375 // verify refs 376 for _, objectIndex := range objMap[classNames[2]] { 377 obj := tenantObjects[objectIndex] 378 379 resp, err := helper.TenantObject(t, classNames[2], obj.ID, obj.Tenant) 380 require.Nil(t, err) 381 require.Equal(t, obj.Class, resp.Class) 382 require.Equal(t, fmt.Sprintf("weaviate://localhost/%s/%v", obj.Class, obj.ID), resp.Properties.(map[string]interface{})[refProps[2]].([]interface{})[0].(map[string]interface{})["beacon"]) 383 require.Equal(t, obj.Tenant, resp.Tenant) 384 } 385 }) 386 387 t.Run("refs between multiple classes class", func(t *testing.T) { 388 var refs []*models.BatchReference 389 for i, objectIndexClass2 := range objMap[classNames[2]] { 390 objClass2 := tenantObjects[objectIndexClass2] 391 // refs between two MMT classes 392 if len(objMap[classNames[1]]) > i { 393 objClass1 := tenantObjects[objMap[classNames[1]][i]] 394 if objClass2.Tenant == objClass1.Tenant { 395 refs = append(refs, &models.BatchReference{ 396 From: strfmt.URI(crossref.NewSource(schema.ClassName(classNames[2]), 397 schema.PropertyName(refProps[1]), objClass2.ID).String()), 398 To: strfmt.URI(crossref.NewLocalhost(classNames[1], objClass1.ID).String()), 399 Tenant: objClass2.Tenant, 400 }) 401 } 402 } 403 404 // refs between MMT and non MMT class 405 if len(objMap[classNames[0]]) > i { 406 objClass0 := tenantObjects[objMap[classNames[0]][i]] 407 refs = append(refs, &models.BatchReference{ 408 From: strfmt.URI(crossref.NewSource(schema.ClassName(classNames[2]), 409 schema.PropertyName(refProps[0]), objClass2.ID).String()), 410 To: strfmt.URI(crossref.NewLocalhost(classNames[0], objClass0.ID).String()), 411 Tenant: objClass2.Tenant, 412 }) 413 } 414 } 415 resp, err := helper.AddReferences(t, refs) 416 helper.CheckReferencesBatchResponse(t, resp, err) 417 418 // verify refs 419 for i, objectIndexClass2 := range objMap[classNames[2]] { 420 objClass2 := tenantObjects[objectIndexClass2] 421 // refs between two MMT classes 422 if len(objMap[classNames[1]]) > i { 423 objClass1 := tenantObjects[objMap[classNames[1]][i]] 424 if objClass2.Tenant != objClass1.Tenant { 425 continue 426 } 427 428 resp, err := helper.TenantObject(t, classNames[2], objClass2.ID, objClass2.Tenant) 429 require.Nil(t, err) 430 require.Equal(t, objClass2.Class, resp.Class) 431 require.Equal(t, fmt.Sprintf("weaviate://localhost/%s/%v", objClass1.Class, objClass1.ID), resp.Properties.(map[string]interface{})[refProps[1]].([]interface{})[0].(map[string]interface{})["beacon"]) 432 require.Equal(t, objClass2.Tenant, resp.Tenant) 433 434 } 435 436 // refs between MMT and non MMT class 437 if len(objMap[classNames[0]]) > i { 438 objClass0 := tenantObjects[objMap[classNames[0]][i]] 439 refs = append(refs, &models.BatchReference{ 440 From: strfmt.URI(crossref.NewSource(schema.ClassName(classNames[2]), 441 schema.PropertyName(refProps[0]), objClass2.ID).String()), 442 To: strfmt.URI(crossref.NewLocalhost(classNames[0], objClass0.ID).String()), 443 Tenant: objClass2.Tenant, 444 }) 445 } 446 } 447 }) 448 }