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  })