storj.io/minio@v0.0.0-20230509071714-0cbc90f649b1/cmd/data-usage_test.go (about)

     1  /*
     2   * MinIO Cloud Storage, (C) 2020 MinIO, Inc.
     3   *
     4   * Licensed under the Apache License, Version 2.0 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   *
     8   *     http://www.apache.org/licenses/LICENSE-2.0
     9   *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   */
    16  
    17  package cmd
    18  
    19  import (
    20  	"bytes"
    21  	"context"
    22  	"fmt"
    23  	"io/ioutil"
    24  	"os"
    25  	"path"
    26  	"path/filepath"
    27  	"testing"
    28  )
    29  
    30  type usageTestFile struct {
    31  	name string
    32  	size int
    33  }
    34  
    35  func TestDataUsageUpdate(t *testing.T) {
    36  	base, err := ioutil.TempDir("", "TestDataUsageUpdate")
    37  	if err != nil {
    38  		t.Skip(err)
    39  	}
    40  	const bucket = "bucket"
    41  	defer os.RemoveAll(base)
    42  	var files = []usageTestFile{
    43  		{name: "rootfile", size: 10000},
    44  		{name: "rootfile2", size: 10000},
    45  		{name: "dir1/d1file", size: 2000},
    46  		{name: "dir2/d2file", size: 300},
    47  		{name: "dir1/dira/dafile", size: 100000},
    48  		{name: "dir1/dira/dbfile", size: 200000},
    49  		{name: "dir1/dira/dirasub/dcfile", size: 1000000},
    50  		{name: "dir1/dira/dirasub/sublevel3/dccccfile", size: 10},
    51  	}
    52  	createUsageTestFiles(t, base, bucket, files)
    53  
    54  	getSize := func(item scannerItem) (sizeS sizeSummary, err error) {
    55  		if item.Typ&os.ModeDir == 0 {
    56  			var s os.FileInfo
    57  			s, err = os.Stat(item.Path)
    58  			if err != nil {
    59  				return
    60  			}
    61  			sizeS.totalSize = s.Size()
    62  			return sizeS, nil
    63  		}
    64  		return
    65  	}
    66  
    67  	got, err := scanDataFolder(context.Background(), base, dataUsageCache{Info: dataUsageCacheInfo{Name: bucket}}, getSize)
    68  	if err != nil {
    69  		t.Fatal(err)
    70  	}
    71  
    72  	// Test dirs
    73  	var want = []struct {
    74  		path       string
    75  		isNil      bool
    76  		size, objs int
    77  		flatten    bool
    78  		oSizes     sizeHistogram
    79  	}{
    80  		{
    81  			path:    "/",
    82  			size:    1322310,
    83  			flatten: true,
    84  			objs:    8,
    85  			oSizes:  sizeHistogram{0: 2, 1: 6},
    86  		},
    87  		{
    88  			path:   "/",
    89  			size:   20000,
    90  			objs:   2,
    91  			oSizes: sizeHistogram{1: 2},
    92  		},
    93  		{
    94  			path:   "/dir1",
    95  			size:   2000,
    96  			objs:   1,
    97  			oSizes: sizeHistogram{1: 1},
    98  		},
    99  		{
   100  			path:    "/dir1/dira",
   101  			flatten: true,
   102  			size:    1300010,
   103  			objs:    4,
   104  			oSizes:  sizeHistogram{0: 1, 1: 3},
   105  		},
   106  		{
   107  			path:    "/dir1/dira/",
   108  			flatten: true,
   109  			size:    1300010,
   110  			objs:    4,
   111  			oSizes:  sizeHistogram{0: 1, 1: 3},
   112  		},
   113  		{
   114  			path:   "/dir1",
   115  			size:   2000,
   116  			objs:   1,
   117  			oSizes: sizeHistogram{0: 0, 1: 1},
   118  		},
   119  		{
   120  			// Children are flattened
   121  			path:   "/dir1/dira/",
   122  			size:   1300010,
   123  			objs:   4,
   124  			oSizes: sizeHistogram{0: 1, 1: 3},
   125  		},
   126  		{
   127  			path:  "/nonexistying",
   128  			isNil: true,
   129  		},
   130  	}
   131  
   132  	for _, w := range want {
   133  		p := path.Join(bucket, w.path)
   134  		t.Run(p, func(t *testing.T) {
   135  			e := got.find(p)
   136  			if w.isNil {
   137  				if e != nil {
   138  					t.Error("want nil, got", e)
   139  				}
   140  				return
   141  			}
   142  			if e == nil {
   143  				t.Fatal("got nil result")
   144  			}
   145  			t.Log(e.Children)
   146  			if w.flatten {
   147  				*e = got.flatten(*e)
   148  			}
   149  			if e.Size != int64(w.size) {
   150  				t.Error("got size", e.Size, "want", w.size)
   151  			}
   152  			if e.Objects != uint64(w.objs) {
   153  				t.Error("got objects", e.Objects, "want", w.objs)
   154  			}
   155  			if e.ObjSizes != w.oSizes {
   156  				t.Error("got histogram", e.ObjSizes, "want", w.oSizes)
   157  			}
   158  		})
   159  	}
   160  
   161  	files = []usageTestFile{
   162  		{
   163  			name: "newfolder/afile",
   164  			size: 4,
   165  		},
   166  		{
   167  			name: "newfolder/anotherone",
   168  			size: 1,
   169  		},
   170  		{
   171  			name: "newfolder/anemptyone",
   172  			size: 0,
   173  		},
   174  		{
   175  			name: "dir1/fileindir1",
   176  			size: 20000,
   177  		},
   178  		{
   179  			name: "dir1/dirc/fileindirc",
   180  			size: 20000,
   181  		},
   182  		{
   183  			name: "rootfile3",
   184  			size: 1000,
   185  		},
   186  	}
   187  	createUsageTestFiles(t, base, bucket, files)
   188  	got, err = scanDataFolder(context.Background(), base, got, getSize)
   189  	if err != nil {
   190  		t.Fatal(err)
   191  	}
   192  
   193  	want = []struct {
   194  		path       string
   195  		isNil      bool
   196  		size, objs int
   197  		flatten    bool
   198  		oSizes     sizeHistogram
   199  	}{
   200  		{
   201  			path:    "/",
   202  			size:    1363315,
   203  			flatten: true,
   204  			objs:    14,
   205  			oSizes:  sizeHistogram{0: 6, 1: 8},
   206  		},
   207  		{
   208  			path:   "/",
   209  			size:   21000,
   210  			objs:   3,
   211  			oSizes: sizeHistogram{0: 1, 1: 2},
   212  		},
   213  		{
   214  			path:   "/newfolder",
   215  			size:   5,
   216  			objs:   3,
   217  			oSizes: sizeHistogram{0: 3},
   218  		},
   219  		{
   220  			path:    "/dir1/dira",
   221  			size:    1300010,
   222  			flatten: true,
   223  			objs:    4,
   224  			oSizes:  sizeHistogram{0: 1, 1: 3},
   225  		},
   226  		{
   227  			path:  "/nonexistying",
   228  			isNil: true,
   229  		},
   230  	}
   231  
   232  	for _, w := range want {
   233  		t.Run(w.path, func(t *testing.T) {
   234  			e := got.find(path.Join(bucket, w.path))
   235  			if w.isNil {
   236  				if e != nil {
   237  					t.Error("want nil, got", e)
   238  				}
   239  				return
   240  			}
   241  			if e == nil {
   242  				t.Fatal("got nil result")
   243  			}
   244  			if w.flatten {
   245  				*e = got.flatten(*e)
   246  			}
   247  			if e.Size != int64(w.size) {
   248  				t.Error("got size", e.Size, "want", w.size)
   249  			}
   250  			if e.Objects != uint64(w.objs) {
   251  				t.Error("got objects", e.Objects, "want", w.objs)
   252  			}
   253  			if e.ObjSizes != w.oSizes {
   254  				t.Error("got histogram", e.ObjSizes, "want", w.oSizes)
   255  			}
   256  		})
   257  	}
   258  
   259  	files = []usageTestFile{
   260  		{
   261  			name: "dir1/dira/dirasub/fileindira2",
   262  			size: 200,
   263  		},
   264  	}
   265  
   266  	createUsageTestFiles(t, base, bucket, files)
   267  	err = os.RemoveAll(filepath.Join(base, bucket, "dir1/dira/dirasub/dcfile"))
   268  	if err != nil {
   269  		t.Fatal(err)
   270  	}
   271  	// Changed dir must be picked up in this many cycles.
   272  	for i := 0; i < dataUsageUpdateDirCycles; i++ {
   273  		got, err = scanDataFolder(context.Background(), base, got, getSize)
   274  		if err != nil {
   275  			t.Fatal(err)
   276  		}
   277  	}
   278  
   279  	want = []struct {
   280  		path       string
   281  		isNil      bool
   282  		size, objs int
   283  		flatten    bool
   284  		oSizes     sizeHistogram
   285  	}{
   286  		{
   287  			path:    "/",
   288  			size:    363515,
   289  			flatten: true,
   290  			objs:    14,
   291  			oSizes:  sizeHistogram{0: 7, 1: 7},
   292  		},
   293  		{
   294  			path:    "/dir1/dira",
   295  			size:    300210,
   296  			objs:    4,
   297  			flatten: true,
   298  			oSizes:  sizeHistogram{0: 2, 1: 2},
   299  		},
   300  	}
   301  
   302  	for _, w := range want {
   303  		p := path.Join(bucket, w.path)
   304  		t.Run(p, func(t *testing.T) {
   305  			e := got.find(p)
   306  			if w.isNil {
   307  				if e != nil {
   308  					t.Error("want nil, got", e)
   309  				}
   310  				return
   311  			}
   312  			if e == nil {
   313  				t.Fatal("got nil result")
   314  			}
   315  			if w.flatten {
   316  				*e = got.flatten(*e)
   317  			}
   318  			if e.Size != int64(w.size) {
   319  				t.Error("got size", e.Size, "want", w.size)
   320  			}
   321  			if e.Objects != uint64(w.objs) {
   322  				t.Error("got objects", e.Objects, "want", w.objs)
   323  			}
   324  			if e.ObjSizes != w.oSizes {
   325  				t.Error("got histogram", e.ObjSizes, "want", w.oSizes)
   326  			}
   327  		})
   328  	}
   329  }
   330  
   331  func TestDataUsageUpdatePrefix(t *testing.T) {
   332  	base, err := ioutil.TempDir("", "TestDataUpdateUsagePrefix")
   333  	if err != nil {
   334  		t.Skip(err)
   335  	}
   336  	base = filepath.Join(base, "bucket")
   337  	defer os.RemoveAll(base)
   338  	var files = []usageTestFile{
   339  		{name: "bucket/rootfile", size: 10000},
   340  		{name: "bucket/rootfile2", size: 10000},
   341  		{name: "bucket/dir1/d1file", size: 2000},
   342  		{name: "bucket/dir2/d2file", size: 300},
   343  		{name: "bucket/dir1/dira/dafile", size: 100000},
   344  		{name: "bucket/dir1/dira/dbfile", size: 200000},
   345  		{name: "bucket/dir1/dira/dirasub/dcfile", size: 1000000},
   346  		{name: "bucket/dir1/dira/dirasub/sublevel3/dccccfile", size: 10},
   347  	}
   348  	createUsageTestFiles(t, base, "", files)
   349  
   350  	getSize := func(item scannerItem) (sizeS sizeSummary, err error) {
   351  		if item.Typ&os.ModeDir == 0 {
   352  			var s os.FileInfo
   353  			s, err = os.Stat(item.Path)
   354  			if err != nil {
   355  				return
   356  			}
   357  			sizeS.totalSize = s.Size()
   358  			return
   359  		}
   360  		return
   361  	}
   362  	got, err := scanDataFolder(context.Background(), base, dataUsageCache{Info: dataUsageCacheInfo{Name: "bucket"}}, getSize)
   363  	if err != nil {
   364  		t.Fatal(err)
   365  	}
   366  	if got.root() == nil {
   367  		t.Log("cached folders:")
   368  		for folder := range got.Cache {
   369  			t.Log("folder:", folder)
   370  		}
   371  		t.Fatal("got nil root.")
   372  	}
   373  
   374  	// Test dirs
   375  	var want = []struct {
   376  		path       string
   377  		isNil      bool
   378  		size, objs int
   379  		oSizes     sizeHistogram
   380  	}{
   381  		{
   382  			path:   "flat",
   383  			size:   1322310,
   384  			objs:   8,
   385  			oSizes: sizeHistogram{0: 2, 1: 6},
   386  		},
   387  		{
   388  			path:   "bucket/",
   389  			size:   20000,
   390  			objs:   2,
   391  			oSizes: sizeHistogram{1: 2},
   392  		},
   393  		{
   394  			path:   "bucket/dir1",
   395  			size:   2000,
   396  			objs:   1,
   397  			oSizes: sizeHistogram{1: 1},
   398  		},
   399  		{
   400  			path:   "bucket/dir1/dira",
   401  			size:   1300010,
   402  			objs:   4,
   403  			oSizes: sizeHistogram{0: 1, 1: 3},
   404  		},
   405  		{
   406  			path:   "bucket/dir1/dira/",
   407  			size:   1300010,
   408  			objs:   4,
   409  			oSizes: sizeHistogram{0: 1, 1: 3},
   410  		},
   411  		{
   412  			path:  "bucket/nonexistying",
   413  			isNil: true,
   414  		},
   415  	}
   416  
   417  	for _, w := range want {
   418  		t.Run(w.path, func(t *testing.T) {
   419  			e := got.find(w.path)
   420  			if w.path == "flat" {
   421  				f := got.flatten(*got.root())
   422  				e = &f
   423  			}
   424  			if w.isNil {
   425  				if e != nil {
   426  					t.Error("want nil, got", e)
   427  				}
   428  				return
   429  			}
   430  			if e == nil {
   431  				t.Fatal("got nil result")
   432  			}
   433  			if e.Size != int64(w.size) {
   434  				t.Error("got size", e.Size, "want", w.size)
   435  			}
   436  			if e.Objects != uint64(w.objs) {
   437  				t.Error("got objects", e.Objects, "want", w.objs)
   438  			}
   439  			if e.ObjSizes != w.oSizes {
   440  				t.Error("got histogram", e.ObjSizes, "want", w.oSizes)
   441  			}
   442  		})
   443  	}
   444  
   445  	files = []usageTestFile{
   446  		{
   447  			name: "bucket/newfolder/afile",
   448  			size: 4,
   449  		},
   450  		{
   451  			name: "bucket/newfolder/anotherone",
   452  			size: 1,
   453  		},
   454  		{
   455  			name: "bucket/newfolder/anemptyone",
   456  			size: 0,
   457  		},
   458  		{
   459  			name: "bucket/dir1/fileindir1",
   460  			size: 20000,
   461  		},
   462  		{
   463  			name: "bucket/dir1/dirc/fileindirc",
   464  			size: 20000,
   465  		},
   466  		{
   467  			name: "bucket/rootfile3",
   468  			size: 1000,
   469  		},
   470  	}
   471  	createUsageTestFiles(t, base, "", files)
   472  	got, err = scanDataFolder(context.Background(), base, got, getSize)
   473  	if err != nil {
   474  		t.Fatal(err)
   475  	}
   476  
   477  	want = []struct {
   478  		path       string
   479  		isNil      bool
   480  		size, objs int
   481  		oSizes     sizeHistogram
   482  	}{
   483  		{
   484  			path:   "flat",
   485  			size:   1363315,
   486  			objs:   14,
   487  			oSizes: sizeHistogram{0: 6, 1: 8},
   488  		},
   489  		{
   490  			path:   "bucket/",
   491  			size:   21000,
   492  			objs:   3,
   493  			oSizes: sizeHistogram{0: 1, 1: 2},
   494  		},
   495  		{
   496  			path:   "bucket/newfolder",
   497  			size:   5,
   498  			objs:   3,
   499  			oSizes: sizeHistogram{0: 3},
   500  		},
   501  		{
   502  			path:   "bucket/dir1/dira",
   503  			size:   1300010,
   504  			objs:   4,
   505  			oSizes: sizeHistogram{0: 1, 1: 3},
   506  		},
   507  		{
   508  			path:  "bucket/nonexistying",
   509  			isNil: true,
   510  		},
   511  	}
   512  
   513  	for _, w := range want {
   514  		t.Run(w.path, func(t *testing.T) {
   515  			e := got.find(w.path)
   516  			if w.path == "flat" {
   517  				f := got.flatten(*got.root())
   518  				e = &f
   519  			}
   520  			if w.isNil {
   521  				if e != nil {
   522  					t.Error("want nil, got", e)
   523  				}
   524  				return
   525  			}
   526  			if e == nil {
   527  				t.Fatal("got nil result")
   528  			}
   529  			if e.Size != int64(w.size) {
   530  				t.Error("got size", e.Size, "want", w.size)
   531  			}
   532  			if e.Objects != uint64(w.objs) {
   533  				t.Error("got objects", e.Objects, "want", w.objs)
   534  			}
   535  			if e.ObjSizes != w.oSizes {
   536  				t.Error("got histogram", e.ObjSizes, "want", w.oSizes)
   537  			}
   538  		})
   539  	}
   540  
   541  	files = []usageTestFile{
   542  		{
   543  			name: "bucket/dir1/dira/dirasub/fileindira2",
   544  			size: 200,
   545  		},
   546  	}
   547  
   548  	createUsageTestFiles(t, base, "", files)
   549  	err = os.RemoveAll(filepath.Join(base, "bucket/dir1/dira/dirasub/dcfile"))
   550  	if err != nil {
   551  		t.Fatal(err)
   552  	}
   553  	// Changed dir must be picked up in this many cycles.
   554  	for i := 0; i < dataUsageUpdateDirCycles; i++ {
   555  		got, err = scanDataFolder(context.Background(), base, got, getSize)
   556  		if err != nil {
   557  			t.Fatal(err)
   558  		}
   559  	}
   560  
   561  	want = []struct {
   562  		path       string
   563  		isNil      bool
   564  		size, objs int
   565  		oSizes     sizeHistogram
   566  	}{
   567  		{
   568  			path:   "flat",
   569  			size:   363515,
   570  			objs:   14,
   571  			oSizes: sizeHistogram{0: 7, 1: 7},
   572  		},
   573  		{
   574  			path:   "bucket/dir1/dira",
   575  			size:   300210,
   576  			objs:   4,
   577  			oSizes: sizeHistogram{0: 2, 1: 2},
   578  		},
   579  	}
   580  
   581  	for _, w := range want {
   582  		t.Run(w.path, func(t *testing.T) {
   583  			e := got.find(w.path)
   584  			if w.path == "flat" {
   585  				f := got.flatten(*got.root())
   586  				e = &f
   587  			}
   588  			if w.isNil {
   589  				if e != nil {
   590  					t.Error("want nil, got", e)
   591  				}
   592  				return
   593  			}
   594  			if e == nil {
   595  				t.Fatal("got nil result")
   596  			}
   597  			if e.Size != int64(w.size) {
   598  				t.Error("got size", e.Size, "want", w.size)
   599  			}
   600  			if e.Objects != uint64(w.objs) {
   601  				t.Error("got objects", e.Objects, "want", w.objs)
   602  			}
   603  			if e.ObjSizes != w.oSizes {
   604  				t.Error("got histogram", e.ObjSizes, "want", w.oSizes)
   605  			}
   606  		})
   607  	}
   608  }
   609  
   610  func createUsageTestFiles(t *testing.T, base, bucket string, files []usageTestFile) {
   611  	for _, f := range files {
   612  		err := os.MkdirAll(filepath.Dir(filepath.Join(base, bucket, f.name)), os.ModePerm)
   613  		if err != nil {
   614  			t.Fatal(err)
   615  		}
   616  		err = ioutil.WriteFile(filepath.Join(base, bucket, f.name), make([]byte, f.size), os.ModePerm)
   617  		if err != nil {
   618  			t.Fatal(err)
   619  		}
   620  	}
   621  }
   622  
   623  func TestDataUsageCacheSerialize(t *testing.T) {
   624  	base, err := ioutil.TempDir("", "TestDataUsageCacheSerialize")
   625  	if err != nil {
   626  		t.Skip(err)
   627  	}
   628  	const bucket = "abucket"
   629  	defer os.RemoveAll(base)
   630  	var files = []usageTestFile{
   631  		{name: "rootfile", size: 10000},
   632  		{name: "rootfile2", size: 10000},
   633  		{name: "dir1/d1file", size: 2000},
   634  		{name: "dir2/d2file", size: 300},
   635  		{name: "dir2/d2file2", size: 300},
   636  		{name: "dir2/d2file3/", size: 300},
   637  		{name: "dir2/d2file4/", size: 300},
   638  		{name: "dir2/d2file5", size: 300},
   639  		{name: "dir1/dira/dafile", size: 100000},
   640  		{name: "dir1/dira/dbfile", size: 200000},
   641  		{name: "dir1/dira/dirasub/dcfile", size: 1000000},
   642  		{name: "dir1/dira/dirasub/sublevel3/dccccfile", size: 10},
   643  		{name: "dir1/dira/dirasub/sublevel3/dccccfile20", size: 20},
   644  		{name: "dir1/dira/dirasub/sublevel3/dccccfile30", size: 30},
   645  		{name: "dir1/dira/dirasub/sublevel3/dccccfile40", size: 40},
   646  	}
   647  	createUsageTestFiles(t, base, bucket, files)
   648  
   649  	getSize := func(item scannerItem) (sizeS sizeSummary, err error) {
   650  		if item.Typ&os.ModeDir == 0 {
   651  			var s os.FileInfo
   652  			s, err = os.Stat(item.Path)
   653  			if err != nil {
   654  				return
   655  			}
   656  			sizeS.totalSize = s.Size()
   657  			return
   658  		}
   659  		return
   660  	}
   661  	want, err := scanDataFolder(context.Background(), base, dataUsageCache{Info: dataUsageCacheInfo{Name: bucket}}, getSize)
   662  	if err != nil {
   663  		t.Fatal(err)
   664  	}
   665  	var buf bytes.Buffer
   666  	err = want.serializeTo(&buf)
   667  	if err != nil {
   668  		t.Fatal(err)
   669  	}
   670  	t.Log("serialized size:", buf.Len(), "bytes")
   671  	var got dataUsageCache
   672  	err = got.deserialize(&buf)
   673  	if err != nil {
   674  		t.Fatal(err)
   675  	}
   676  	if got.Info.LastUpdate.IsZero() {
   677  		t.Error("lastupdate not set")
   678  	}
   679  
   680  	if !want.Info.LastUpdate.Equal(got.Info.LastUpdate) {
   681  		t.Fatalf("deserialize LastUpdate mismatch\nwant: %+v\ngot:  %+v", want, got)
   682  	}
   683  	if len(want.Cache) != len(got.Cache) {
   684  		t.Errorf("deserialize mismatch length\nwant: %+v\ngot:  %+v", len(want.Cache), len(got.Cache))
   685  	}
   686  	for wkey, wval := range want.Cache {
   687  		gotv := got.Cache[wkey]
   688  		if fmt.Sprint(gotv) != fmt.Sprint(wval) {
   689  			t.Errorf("deserialize mismatch, key %v\nwant: %+v\ngot:  %+v", wkey, wval, gotv)
   690  		}
   691  	}
   692  
   693  }