github.com/weaviate/weaviate@v1.24.6/test/helper/journey/backup_journey.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 journey 13 14 import ( 15 "math/rand" 16 "testing" 17 "time" 18 19 "github.com/stretchr/testify/assert" 20 "github.com/stretchr/testify/require" 21 "github.com/weaviate/weaviate/entities/backup" 22 "github.com/weaviate/weaviate/entities/models" 23 "github.com/weaviate/weaviate/entities/schema" 24 "github.com/weaviate/weaviate/test/helper" 25 moduleshelper "github.com/weaviate/weaviate/test/helper/modules" 26 ) 27 28 type journeyType int 29 30 const ( 31 singleNodeJourney journeyType = iota 32 clusterJourney 33 ) 34 35 type dataIntegrityCheck int 36 37 const ( 38 checkClassPresenceOnly = iota 39 checkClassAndDataPresence 40 ) 41 42 const ( 43 singleTenant = "" 44 multiTenant = true 45 ) 46 47 func backupJourney(t *testing.T, className, backend, backupID string, 48 journeyType journeyType, dataIntegrityCheck dataIntegrityCheck, 49 tenantNames []string, pqEnabled bool, 50 ) { 51 if journeyType == clusterJourney && backend == "filesystem" { 52 t.Run("should fail backup/restore with local filesystem backend", func(t *testing.T) { 53 backupResp, err := helper.CreateBackup(t, helper.DefaultBackupConfig(), className, backend, backupID) 54 assert.Nil(t, backupResp) 55 assert.Error(t, err) 56 57 restoreResp, err := helper.RestoreBackup(t, helper.DefaultRestoreConfig(), className, backend, backupID, map[string]string{}) 58 assert.Nil(t, restoreResp) 59 assert.Error(t, err) 60 }) 61 return 62 } 63 64 t.Run("create backup", func(t *testing.T) { 65 resp, err := helper.CreateBackup(t, helper.DefaultBackupConfig(), className, backend, backupID) 66 helper.AssertRequestOk(t, resp, err, nil) 67 // wait for create success 68 createTime := time.Now() 69 for { 70 if time.Now().After(createTime.Add(time.Minute)) { 71 break 72 } 73 74 resp, err := helper.CreateBackupStatus(t, backend, backupID) 75 helper.AssertRequestOk(t, resp, err, func() { 76 require.NotNil(t, resp) 77 require.NotNil(t, resp.Payload) 78 require.NotNil(t, resp.Payload.Status) 79 }) 80 81 if *resp.Payload.Status == string(backup.Success) { 82 break 83 } 84 time.Sleep(time.Second * 1) 85 } 86 87 statusResp, err := helper.CreateBackupStatus(t, backend, backupID) 88 helper.AssertRequestOk(t, resp, err, func() { 89 require.NotNil(t, statusResp) 90 require.NotNil(t, statusResp.Payload) 91 require.NotNil(t, statusResp.Payload.Status) 92 }) 93 94 require.Equal(t, *statusResp.Payload.Status, 95 string(backup.Success), statusResp.Payload.Error) 96 }) 97 98 t.Run("delete class for restoration", func(t *testing.T) { 99 helper.DeleteClass(t, className) 100 }) 101 102 t.Run("restore backup", func(t *testing.T) { 103 _, err := helper.RestoreBackup(t, helper.DefaultRestoreConfig(), className, backend, backupID, map[string]string{}) 104 require.Nil(t, err, "expected nil, got: %v", err) 105 106 // wait for restore success 107 restoreTime := time.Now() 108 for { 109 if time.Now().After(restoreTime.Add(time.Minute)) { 110 break 111 } 112 113 resp, err := helper.RestoreBackupStatus(t, backend, backupID) 114 helper.AssertRequestOk(t, resp, err, func() { 115 require.NotNil(t, resp) 116 require.NotNil(t, resp.Payload) 117 require.NotNil(t, resp.Payload.Status) 118 }) 119 120 if *resp.Payload.Status == string(backup.Success) { 121 break 122 } 123 124 time.Sleep(time.Second) 125 } 126 127 statusResp, err := helper.RestoreBackupStatus(t, backend, backupID) 128 helper.AssertRequestOk(t, statusResp, err, func() { 129 require.NotNil(t, statusResp) 130 require.NotNil(t, statusResp.Payload) 131 require.NotNil(t, statusResp.Payload.Status) 132 }) 133 134 require.Equal(t, *statusResp.Payload.Status, string(backup.Success)) 135 }) 136 137 // assert class exists again it its entirety 138 if tenantNames != nil { 139 for _, name := range tenantNames { 140 moduleshelper.EnsureClassExists(t, className, name) 141 if dataIntegrityCheck == checkClassAndDataPresence { 142 count := moduleshelper.GetClassCount(t, className, name) 143 assert.Equal(t, int64(500/len(tenantNames)), count) 144 } 145 } 146 } else { 147 moduleshelper.EnsureClassExists(t, className, singleTenant) 148 if dataIntegrityCheck == checkClassAndDataPresence { 149 count := moduleshelper.GetClassCount(t, className, singleTenant) 150 assert.Equal(t, int64(500), count) 151 if pqEnabled { 152 moduleshelper.EnsureCompressedVectorsRestored(t, className) 153 } 154 } 155 } 156 } 157 158 func nodeMappingBackupJourney_Backup(t *testing.T, className, backend, backupID string, tenantNames []string, 159 ) { 160 t.Run("create backup", func(t *testing.T) { 161 resp, err := helper.CreateBackup(t, helper.DefaultBackupConfig(), className, backend, backupID) 162 helper.AssertRequestOk(t, resp, err, nil) 163 // wait for create success 164 createTime := time.Now() 165 for { 166 if time.Now().After(createTime.Add(time.Minute)) { 167 break 168 } 169 170 resp, err := helper.CreateBackupStatus(t, backend, backupID) 171 helper.AssertRequestOk(t, resp, err, func() { 172 require.NotNil(t, resp) 173 require.NotNil(t, resp.Payload) 174 require.NotNil(t, resp.Payload.Status) 175 }) 176 177 if *resp.Payload.Status == string(backup.Success) { 178 break 179 } 180 time.Sleep(time.Second * 1) 181 } 182 183 statusResp, err := helper.CreateBackupStatus(t, backend, backupID) 184 helper.AssertRequestOk(t, resp, err, func() { 185 require.NotNil(t, statusResp) 186 require.NotNil(t, statusResp.Payload) 187 require.NotNil(t, statusResp.Payload.Status) 188 }) 189 190 require.Equal(t, *statusResp.Payload.Status, string(backup.Success)) 191 }) 192 } 193 194 func nodeMappingBackupJourney_Restore(t *testing.T, className, backend, backupID string, tenantNames []string, nodeMapping map[string]string) { 195 t.Run("restore backup", func(t *testing.T) { 196 _, err := helper.RestoreBackup(t, helper.DefaultRestoreConfig(), className, backend, backupID, nodeMapping) 197 require.Nil(t, err, "expected nil, got: %v", err) 198 199 // wait for restore success 200 restoreTime := time.Now() 201 for { 202 if time.Now().After(restoreTime.Add(time.Minute)) { 203 break 204 } 205 206 resp, err := helper.RestoreBackupStatus(t, backend, backupID) 207 helper.AssertRequestOk(t, resp, err, func() { 208 require.NotNil(t, resp) 209 require.NotNil(t, resp.Payload) 210 require.NotNil(t, resp.Payload.Status) 211 }) 212 213 if *resp.Payload.Status == string(backup.Success) { 214 break 215 } 216 217 time.Sleep(time.Second) 218 } 219 220 statusResp, err := helper.RestoreBackupStatus(t, backend, backupID) 221 helper.AssertRequestOk(t, statusResp, err, func() { 222 require.NotNil(t, statusResp) 223 require.NotNil(t, statusResp.Payload) 224 require.NotNil(t, statusResp.Payload.Status) 225 }) 226 227 require.Equal(t, *statusResp.Payload.Status, string(backup.Success)) 228 }) 229 230 // assert class exists again it its entirety 231 if tenantNames != nil { 232 for _, name := range tenantNames { 233 count := moduleshelper.GetClassCount(t, className, name) 234 assert.Equal(t, int64(500/len(tenantNames)), count) 235 } 236 } else { 237 count := moduleshelper.GetClassCount(t, className, singleTenant) 238 assert.Equal(t, int64(500), count) 239 } 240 } 241 242 func addTestClass(t *testing.T, className string, multiTenant bool) { 243 class := &models.Class{ 244 Class: className, 245 ModuleConfig: map[string]interface{}{ 246 "text2vec-contextionary": map[string]interface{}{ 247 "vectorizeClassName": true, 248 }, 249 }, 250 Properties: []*models.Property{ 251 { 252 Name: "contents", 253 DataType: schema.DataTypeText.PropString(), 254 Tokenization: models.PropertyTokenizationWhitespace, 255 }, 256 }, 257 } 258 259 if multiTenant { 260 class.MultiTenancyConfig = &models.MultiTenancyConfig{ 261 Enabled: true, 262 } 263 } 264 265 helper.CreateClass(t, class) 266 } 267 268 func addTestObjects(t *testing.T, className string, tenantNames []string) { 269 const ( 270 noteLengthMin = 4 271 noteLengthMax = 1024 272 273 batchSize = 10 274 numBatches = 50 275 ) 276 277 seededRand := rand.New(rand.NewSource(time.Now().UnixNano())) 278 multiTenant := len(tenantNames) > 0 279 280 for i := 0; i < numBatches; i++ { 281 batch := make([]*models.Object, batchSize) 282 for j := 0; j < batchSize; j++ { 283 contentsLength := noteLengthMin + seededRand.Intn(noteLengthMax-noteLengthMin+1) 284 contents := helper.GetRandomString(contentsLength) 285 286 obj := models.Object{ 287 Class: className, 288 Properties: map[string]interface{}{"contents": contents}, 289 } 290 if multiTenant { 291 obj.Tenant = tenantNames[i] 292 } 293 batch[j] = &obj 294 } 295 helper.CreateObjectsBatch(t, batch) 296 297 } 298 }