github.com/NVIDIA/aistore@v1.3.23-0.20240517131212-7df6609be51d/ais/prxlsobj_internal_test.go (about) 1 // Package ais provides core functionality for the AIStore object storage. 2 /* 3 * Copyright (c) 2018-2023, NVIDIA CORPORATION. All rights reserved. 4 */ 5 package ais 6 7 import ( 8 "github.com/NVIDIA/aistore/cmn" 9 . "github.com/onsi/ginkgo/v2" 10 . "github.com/onsi/gomega" 11 ) 12 13 var _ = Describe("ListObjectsCache+ListObjectsBuffer", func() { 14 makeEntries := func(xs ...string) (entries cmn.LsoEntries) { 15 for _, x := range xs { 16 entries = append(entries, &cmn.LsoEnt{ 17 Name: x, 18 }) 19 } 20 return 21 } 22 23 extractNames := func(entries cmn.LsoEntries) (xs []string) { 24 for _, entry := range entries { 25 xs = append(xs, entry.Name) 26 } 27 return 28 } 29 30 Describe("ListObjectsCache", func() { 31 var ( 32 id = cacheReqID{bck: &cmn.Bck{Name: "some_bck"}} 33 cache *lsobjCaches 34 ) 35 36 BeforeEach(func() { 37 cache = &lsobjCaches{} 38 }) 39 40 It("should correctly add entries to cache", func() { 41 cache.set(id, "", makeEntries("a", "b", "c"), 3) 42 entries, hasEnough := cache.get(id, "", 3) 43 Expect(hasEnough).To(BeTrue()) 44 Expect(extractNames(entries)).To(Equal([]string{"a", "b", "c"})) 45 }) 46 47 It("should correctly add streaming intervals", func() { 48 cache.set(id, "", makeEntries("a", "b", "c"), 3) 49 cache.set(id, "c", makeEntries("d", "e", "f"), 3) 50 cache.set(id, "f", makeEntries("g", "h", "i"), 3) 51 52 entries, hasEnough := cache.get(id, "", 9) 53 Expect(hasEnough).To(BeTrue()) 54 Expect(extractNames(entries)).To(Equal([]string{"a", "b", "c", "d", "e", "f", "g", "h", "i"})) 55 }) 56 57 It("should correctly handle last page", func() { 58 cache.set(id, "", makeEntries("a", "b", "c"), 3) 59 cache.set(id, "c", makeEntries("d", "e", "f"), 3) 60 cache.set(id, "f", makeEntries("g", "h", "i"), 4) 61 62 entries, hasEnough := cache.get(id, "", 10) 63 Expect(hasEnough).To(BeTrue()) 64 Expect(extractNames(entries)).To(Equal([]string{"a", "b", "c", "d", "e", "f", "g", "h", "i"})) 65 }) 66 67 It("should correctly handle empty last page", func() { 68 cache.set(id, "", makeEntries("a", "b", "c"), 3) 69 cache.set(id, "c", makeEntries("d", "e", "f"), 3) 70 cache.set(id, "f", cmn.LsoEntries{}, 4) 71 72 entries, hasEnough := cache.get(id, "", 10) 73 Expect(hasEnough).To(BeTrue()) 74 Expect(extractNames(entries)).To(Equal([]string{"a", "b", "c", "d", "e", "f"})) 75 }) 76 77 It("should correctly handle overlapping entries", func() { 78 cache.set(id, "", makeEntries("a", "b", "c"), 3) 79 cache.set(id, "a", makeEntries("d", "e", "f"), 3) 80 81 entries, hasEnough := cache.get(id, "", 4) 82 Expect(hasEnough).To(BeTrue()) 83 Expect(extractNames(entries)).To(Equal([]string{"a", "d", "e", "f"})) 84 }) 85 86 It("should correctly merge intervals", func() { 87 cache.set(id, "", makeEntries("a", "b", "c"), 3) 88 cache.set(id, "g", makeEntries("h", "i", "j"), 3) 89 90 _, hasEnough := cache.get(id, "", 3) 91 Expect(hasEnough).To(BeTrue()) 92 _, hasEnough = cache.get(id, "", 4) 93 Expect(hasEnough).To(BeFalse()) 94 95 _, hasEnough = cache.get(id, "g", 2) 96 Expect(hasEnough).To(BeTrue()) 97 98 // Add interval in the middle. 99 cache.set(id, "c", makeEntries("d", "e", "f", "g"), 4) 100 101 // Check that now intervals are connected. 102 entries, hasEnough := cache.get(id, "", 4) 103 Expect(hasEnough).To(BeTrue()) 104 Expect(extractNames(entries)).To(Equal([]string{"a", "b", "c", "d"})) 105 entries, hasEnough = cache.get(id, "", 10) 106 Expect(hasEnough).To(BeTrue()) 107 Expect(extractNames(entries)).To(Equal([]string{"a", "b", "c", "d", "e", "f", "g", "h", "i", "j"})) 108 }) 109 110 It("should correctly merge overlapping intervals", func() { 111 cache.set(id, "", makeEntries("a", "b", "c"), 3) 112 cache.set(id, "g", makeEntries("h", "i", "j"), 3) 113 cache.set(id, "a", makeEntries("b", "c", "d", "e", "f", "g", "h", "i"), 8) 114 115 entries, hasEnough := cache.get(id, "", 10) 116 Expect(hasEnough).To(BeTrue()) 117 Expect(extractNames(entries)).To(Equal([]string{"a", "b", "c", "d", "e", "f", "g", "h", "i", "j"})) 118 }) 119 120 Describe("prepend", func() { 121 It("should correctly prepend interval", func() { 122 cache.set(id, "c", makeEntries("d", "e", "f"), 3) 123 cache.set(id, "", makeEntries("a", "b", "c"), 3) 124 125 entries, hasEnough := cache.get(id, "", 6) 126 Expect(hasEnough).To(BeTrue()) 127 Expect(extractNames(entries)).To(Equal([]string{"a", "b", "c", "d", "e", "f"})) 128 }) 129 130 It("should correctly prepend overlapping intervals", func() { 131 cache.set(id, "c", makeEntries("d", "e", "f"), 3) 132 cache.set(id, "", makeEntries("a", "b", "c", "d", "e"), 5) 133 134 entries, hasEnough := cache.get(id, "", 6) 135 Expect(hasEnough).To(BeTrue()) 136 Expect(extractNames(entries)).To(Equal([]string{"a", "b", "c", "d", "e", "f"})) 137 }) 138 }) 139 140 It("should discard interval if already exists", func() { 141 cache.set(id, "", makeEntries("a", "b", "c", "d", "e"), 5) 142 cache.set(id, "a", makeEntries("b", "c", "d"), 3) 143 144 entries, hasEnough := cache.get(id, "", 5) 145 Expect(hasEnough).To(BeTrue()) 146 Expect(extractNames(entries)).To(Equal([]string{"a", "b", "c", "d", "e"})) 147 }) 148 149 It("should discard interval if already exists", func() { 150 cache.set(id, "", makeEntries("a", "b", "c", "d", "e"), 5) 151 cache.set(id, "", makeEntries("a", "b"), 2) 152 153 entries, hasEnough := cache.get(id, "", 5) 154 Expect(hasEnough).To(BeTrue()) 155 Expect(extractNames(entries)).To(Equal([]string{"a", "b", "c", "d", "e"})) 156 }) 157 158 It("should return empty response if cache is empty", func() { 159 entries, hasEnough := cache.get(id, "", 0) 160 Expect(hasEnough).To(BeFalse()) 161 Expect(entries).To(BeNil()) 162 163 entries, hasEnough = cache.get(id, "", 1) 164 Expect(hasEnough).To(BeFalse()) 165 Expect(entries).To(BeNil()) 166 167 entries, hasEnough = cache.get(id, "a", 1) 168 Expect(hasEnough).To(BeFalse()) 169 Expect(entries).To(BeNil()) 170 }) 171 172 It("should correctly distinguish between different caches", func() { 173 otherID := cacheReqID{bck: &cmn.Bck{Name: "something"}} 174 175 cache.set(id, "", makeEntries("a", "b", "c"), 3) 176 entries, hasEnough := cache.get(id, "", 3) 177 Expect(hasEnough).To(BeTrue()) 178 Expect(extractNames(entries)).To(Equal([]string{"a", "b", "c"})) 179 180 // Check if `otherID` cache is empty. 181 entries, hasEnough = cache.get(otherID, "", 3) 182 Expect(hasEnough).To(BeFalse()) 183 Expect(entries).To(BeNil()) 184 185 cache.set(otherID, "", makeEntries("d", "e", "f"), 3) 186 entries, hasEnough = cache.get(otherID, "", 3) 187 Expect(hasEnough).To(BeTrue()) 188 Expect(extractNames(entries)).To(Equal([]string{"d", "e", "f"})) 189 }) 190 191 Describe("prefix", func() { 192 It("should get prefixed entries from `id='bck'` cache", func() { 193 prefixID := cacheReqID{bck: id.bck, prefix: "p-"} 194 195 cache.set(id, "", makeEntries("a", "p-b", "p-c", "p-d", "z"), 5) 196 entries, hasEnough := cache.get(id, "", 3) 197 Expect(hasEnough).To(BeTrue()) 198 Expect(extractNames(entries)).To(Equal([]string{"a", "p-b", "p-c"})) 199 200 // Now check that getting for prefix works. 201 entries, hasEnough = cache.get(prefixID, "", 3) 202 Expect(hasEnough).To(BeTrue()) 203 Expect(extractNames(entries)).To(Equal([]string{"p-b", "p-c", "p-d"})) 204 205 entries, hasEnough = cache.get(prefixID, "", 2) 206 Expect(hasEnough).To(BeTrue()) 207 Expect(extractNames(entries)).To(Equal([]string{"p-b", "p-c"})) 208 209 // User requests more than we have but also all the prefixes are 210 // fully contained in the interval so we are sure that there isn't 211 // more of them. Therefore, we should return what we have. 212 entries, hasEnough = cache.get(prefixID, "", 4) 213 Expect(hasEnough).To(BeTrue()) 214 Expect(extractNames(entries)).To(Equal([]string{"p-b", "p-c", "p-d"})) 215 }) 216 217 It("should get prefixed entries from `id='bck' cache (boundaries)", func() { 218 cache.set(id, "", makeEntries("b", "d", "y"), 3) 219 entries, hasEnough := cache.get(id, "", 3) 220 Expect(hasEnough).To(BeTrue()) 221 Expect(extractNames(entries)).To(Equal([]string{"b", "d", "y"})) 222 223 // Get entries with prefix `y`. 224 entries, hasEnough = cache.get(cacheReqID{bck: id.bck, prefix: "y"}, "", 1) 225 Expect(hasEnough).To(BeTrue()) 226 Expect(extractNames(entries)).To(Equal([]string{"y"})) 227 228 _, hasEnough = cache.get(cacheReqID{bck: id.bck, prefix: "y"}, "", 2) 229 Expect(hasEnough).To(BeFalse()) 230 231 // Get entries with prefix `b`. 232 entries, hasEnough = cache.get(cacheReqID{bck: id.bck, prefix: "b"}, "", 1) 233 Expect(hasEnough).To(BeTrue()) 234 Expect(extractNames(entries)).To(Equal([]string{"b"})) 235 236 entries, hasEnough = cache.get(cacheReqID{bck: id.bck, prefix: "b"}, "", 2) 237 Expect(hasEnough).To(BeTrue()) 238 Expect(extractNames(entries)).To(Equal([]string{"b"})) 239 240 // Get entries with prefix `a`. 241 entries, hasEnough = cache.get(cacheReqID{bck: id.bck, prefix: "a"}, "", 1) 242 Expect(hasEnough).To(BeTrue()) 243 Expect(entries).To(Equal(cmn.LsoEntries{})) 244 245 entries, hasEnough = cache.get(cacheReqID{bck: id.bck, prefix: "a"}, "", 2) 246 Expect(hasEnough).To(BeTrue()) 247 Expect(entries).To(Equal(cmn.LsoEntries{})) 248 249 // Make interval "last". 250 cache.set(id, "y", makeEntries(), 1) 251 252 // Get entries with prefix `y`. 253 entries, hasEnough = cache.get(cacheReqID{bck: id.bck, prefix: "y"}, "", 1) 254 Expect(hasEnough).To(BeTrue()) 255 Expect(extractNames(entries)).To(Equal([]string{"y"})) 256 257 entries, hasEnough = cache.get(cacheReqID{bck: id.bck, prefix: "y"}, "", 2) 258 Expect(hasEnough).To(BeTrue()) 259 Expect(extractNames(entries)).To(Equal([]string{"y"})) 260 261 // Get entries with prefix `ya`. 262 entries, hasEnough = cache.get(cacheReqID{bck: id.bck, prefix: "ya"}, "", 1) 263 Expect(hasEnough).To(BeTrue()) 264 Expect(entries).To(Equal(cmn.LsoEntries{})) 265 }) 266 267 It("should correctly behave in `id='bck'` cache if prefix is contained in interval but there aren't matching entries", func() { 268 prefixID := cacheReqID{bck: id.bck, prefix: "b-"} 269 270 cache.set(id, "", makeEntries("a", "p-b", "p-c", "p-d", "z"), 5) 271 entries, hasEnough := cache.get(id, "", 3) 272 Expect(hasEnough).To(BeTrue()) 273 Expect(extractNames(entries)).To(Equal([]string{"a", "p-b", "p-c"})) 274 275 // It should correctly return no entries when prefixes are 276 // contained in the interval but there is no such entries. 277 entries, hasEnough = cache.get(prefixID, "", 2) 278 Expect(hasEnough).To(BeTrue()) 279 Expect(entries).To(Equal(cmn.LsoEntries{})) 280 281 entries, hasEnough = cache.get(prefixID, "a", 2) 282 Expect(hasEnough).To(BeTrue()) 283 Expect(entries).To(Equal(cmn.LsoEntries{})) 284 }) 285 286 It("should correctly behave in `id='bck'` cache if prefix is out of the interval", func() { 287 cache.set(id, "", makeEntries("b", "m", "p", "y"), 4) 288 entries, hasEnough := cache.get(id, "", 3) 289 Expect(hasEnough).To(BeTrue()) 290 Expect(extractNames(entries)).To(Equal([]string{"b", "m", "p"})) 291 292 _, hasEnough = cache.get(cacheReqID{bck: id.bck, prefix: "z"}, "", 1) 293 Expect(hasEnough).To(BeFalse()) 294 }) 295 296 It("should get prefixed entries from `id='bck+prefix'` cache", func() { 297 prefixID := cacheReqID{bck: id.bck, prefix: "p-"} 298 299 cache.set(id, "", makeEntries("p-a", "p-b", "p-c", "p-d", "p-e"), 5) 300 entries, hasEnough := cache.get(prefixID, "", 3) 301 Expect(hasEnough).To(BeTrue()) 302 Expect(extractNames(entries)).To(Equal([]string{"p-a", "p-b", "p-c"})) 303 304 // Now check that getting for prefix works. 305 entries, hasEnough = cache.get(prefixID, "p-b", 3) 306 Expect(hasEnough).To(BeTrue()) 307 Expect(extractNames(entries)).To(Equal([]string{"p-c", "p-d", "p-e"})) 308 309 _, hasEnough = cache.get(prefixID, "p-b", 4) 310 Expect(hasEnough).To(BeFalse()) 311 }) 312 313 It("should fallback to `id='bck'` cache when there is not enough entries in `id='bck+prefix'` cache", func() { 314 prefixID := cacheReqID{bck: id.bck, prefix: "p-"} 315 316 cache.set(id, "", makeEntries("a", "p-b", "p-c", "p-d"), 4) 317 cache.set(prefixID, "", makeEntries("p-b", "p-c"), 2) 318 319 entries, hasEnough := cache.get(prefixID, "", 3) 320 Expect(hasEnough).To(BeTrue()) 321 Expect(extractNames(entries)).To(Equal([]string{"p-b", "p-c", "p-d"})) 322 323 // Insert more into `id="bck+prefix"` cache end check that we can get from it. 324 cache.set(prefixID, "p-c", makeEntries("p-d", "p-f", "p-g"), 3) 325 entries, hasEnough = cache.get(prefixID, "", 5) 326 Expect(hasEnough).To(BeTrue()) 327 Expect(extractNames(entries)).To(Equal([]string{"p-b", "p-c", "p-d", "p-f", "p-g"})) 328 }) 329 }) 330 }) 331 332 Describe("ListObjectsBuffer", func() { 333 var ( 334 id = "some_id" 335 buffer *lsobjBuffers 336 ) 337 338 BeforeEach(func() { 339 buffer = &lsobjBuffers{} 340 }) 341 342 It("should correctly create single buffer", func() { 343 buffer.set(id, "target1", makeEntries("a", "d", "g"), 3) 344 buffer.set(id, "target2", makeEntries("b", "c", "h"), 3) 345 buffer.set(id, "target3", makeEntries("e", "f"), 2) 346 347 entries, hasEnough := buffer.get(id, "", 6) 348 Expect(hasEnough).To(BeTrue()) 349 Expect(extractNames(entries)).To(Equal([]string{"a", "b", "c", "d", "e", "f"})) 350 351 // Since `f` is the smallest of the last elements we cannot join: 352 // `g` and `h` because there might be still some elements after `f` 353 // in `target3`. Therefore, we should answer "not enough" to next calls. 354 entries, hasEnough = buffer.get(id, "f", 1) 355 Expect(hasEnough).To(BeFalse()) 356 Expect(entries).To(BeNil()) 357 }) 358 359 It("should correctly append new entries", func() { 360 buffer.set(id, "target1", makeEntries("a", "d", "g"), 3) 361 buffer.set(id, "target2", makeEntries("b", "c", "h"), 3) 362 buffer.set(id, "target3", makeEntries("e", "f"), 2) 363 364 entries, hasEnough := buffer.get(id, "", 3) 365 Expect(hasEnough).To(BeTrue()) 366 Expect(extractNames(entries)).To(Equal([]string{"a", "b", "c"})) 367 entries, hasEnough = buffer.get(id, "c", 4) 368 Expect(hasEnough).To(BeFalse()) 369 Expect(entries).To(BeNil()) 370 371 last := buffer.last(id, "c") 372 // `f` is the smallest of the last elements of all targets, so it 373 // should be next continuation token. 374 Expect(last).To(Equal("f")) 375 376 // Now we simulate receiving entries after `f` token. 377 buffer.set(id, "target1", makeEntries("g", "k", "m"), 3) 378 buffer.set(id, "target2", makeEntries("h", "i", "n"), 3) 379 buffer.set(id, "target3", makeEntries("j", "l"), 2) 380 381 entries, hasEnough = buffer.get(id, "c", 9) 382 Expect(hasEnough).To(BeTrue()) 383 Expect(extractNames(entries)).To(Equal([]string{"d", "e", "f", "g", "h", "i", "j", "k", "l"})) 384 entries, hasEnough = buffer.get(id, "l", 1) 385 Expect(hasEnough).To(BeFalse()) 386 Expect(entries).To(BeNil()) 387 }) 388 389 It("should correctly identify no objects", func() { 390 entries, hasEnough := buffer.get("id", "a", 10) 391 Expect(hasEnough).To(BeFalse()) 392 Expect(entries).To(BeNil()) 393 entries, hasEnough = buffer.get("id", "a", 10) 394 Expect(hasEnough).To(BeFalse()) 395 Expect(entries).To(BeNil()) 396 }) 397 398 It("should correctly handle end of entries", func() { 399 buffer.set(id, "target1", makeEntries("a", "d", "e"), 4) 400 buffer.set(id, "target2", makeEntries("b", "c", "f"), 4) 401 402 entries, hasEnough := buffer.get(id, "", 7) 403 Expect(hasEnough).To(BeTrue()) 404 Expect(extractNames(entries)).To(Equal([]string{"a", "b", "c", "d", "e", "f"})) 405 406 entries, hasEnough = buffer.get(id, "f", 1) 407 Expect(hasEnough).To(BeTrue()) 408 Expect(entries).To(HaveLen(0)) 409 }) 410 411 It("should correctly handle getting 0 entries", func() { 412 buffer.set(id, "target1", makeEntries(), 2) 413 buffer.set(id, "target2", makeEntries(), 2) 414 415 entries, hasEnough := buffer.get(id, "", 7) 416 Expect(hasEnough).To(BeTrue()) 417 Expect(entries).To(HaveLen(0)) 418 419 entries, hasEnough = buffer.get(id, "f", 1) 420 Expect(hasEnough).To(BeTrue()) 421 Expect(entries).To(HaveLen(0)) 422 }) 423 424 It("should correctly handle rerequesting the page", func() { 425 buffer.set(id, "target1", makeEntries("a", "d", "g"), 3) 426 buffer.set(id, "target2", makeEntries("b", "c", "h"), 3) 427 buffer.set(id, "target3", makeEntries("e", "f"), 2) 428 429 entries, hasEnough := buffer.get(id, "", 2) 430 Expect(hasEnough).To(BeTrue()) 431 Expect(extractNames(entries)).To(Equal([]string{"a", "b"})) 432 entries, hasEnough = buffer.get(id, "b", 3) 433 Expect(hasEnough).To(BeTrue()) 434 Expect(extractNames(entries)).To(Equal([]string{"c", "d", "e"})) 435 436 // Rerequest the page with token `b`. 437 _, hasEnough = buffer.get(id, "b", 3) 438 Expect(hasEnough).To(BeFalse()) 439 440 // Simulate targets resending data. 441 buffer.set(id, "target1", makeEntries("d", "g"), 2) 442 buffer.set(id, "target2", makeEntries("c", "h"), 2) 443 buffer.set(id, "target3", makeEntries("e", "f"), 2) 444 445 entries, hasEnough = buffer.get(id, "b", 3) 446 Expect(hasEnough).To(BeTrue()) 447 Expect(extractNames(entries)).To(Equal([]string{"c", "d", "e"})) 448 entries, hasEnough = buffer.get(id, "e", 1) 449 Expect(hasEnough).To(BeTrue()) 450 Expect(extractNames(entries)).To(Equal([]string{"f"})) 451 _, hasEnough = buffer.get(id, "f", 1) 452 Expect(hasEnough).To(BeFalse()) 453 }) 454 }) 455 })