github.com/NVIDIA/aistore@v1.3.23-0.20240517131212-7df6609be51d/fs/fqn_test.go (about)

     1  // Package fs_test provides tests for fs package
     2  /*
     3   * Copyright (c) 2018-2024, NVIDIA CORPORATION. All rights reserved.
     4   */
     5  package fs_test
     6  
     7  import (
     8  	"os"
     9  	"strings"
    10  	"testing"
    11  
    12  	"github.com/NVIDIA/aistore/api/apc"
    13  	"github.com/NVIDIA/aistore/cmn"
    14  	"github.com/NVIDIA/aistore/cmn/cos"
    15  	"github.com/NVIDIA/aistore/core/mock"
    16  	"github.com/NVIDIA/aistore/fs"
    17  	"github.com/NVIDIA/aistore/tools/tassert"
    18  )
    19  
    20  func TestParseFQN(t *testing.T) {
    21  	const tmpMpath = "/tmp/ais-fqn-test"
    22  	tests := []struct {
    23  		testName        string
    24  		fqn             string
    25  		mpaths          []string
    26  		wantMPath       string
    27  		wantBck         cmn.Bck
    28  		wantContentType string
    29  		wantObjName     string
    30  		wantErr         bool
    31  		wantAddErr      bool
    32  	}{
    33  		// good
    34  		{
    35  			"smoke test",
    36  			tmpMpath + "/@ais/#namespace/bucket/%ob/objname",
    37  			[]string{tmpMpath},
    38  			tmpMpath,
    39  			cmn.Bck{Name: "bucket", Provider: apc.AIS, Ns: cmn.Ns{Name: "namespace"}},
    40  			fs.ObjectType, "objname", false,
    41  			false,
    42  		},
    43  		{
    44  			"smoke test (namespace global)",
    45  			tmpMpath + "/@ais/bucket/%ob/objname",
    46  			[]string{tmpMpath},
    47  			tmpMpath,
    48  			cmn.Bck{Name: "bucket", Provider: apc.AIS, Ns: cmn.NsGlobal},
    49  			fs.ObjectType, "objname", false,
    50  			false,
    51  		},
    52  		{
    53  			"content type (work)",
    54  			tmpMpath + "/@aws/bucket/%wk/objname",
    55  			[]string{tmpMpath},
    56  			tmpMpath,
    57  			cmn.Bck{Name: "bucket", Provider: apc.AWS, Ns: cmn.NsGlobal},
    58  			fs.WorkfileType, "objname", false,
    59  			false,
    60  		},
    61  		{
    62  			"cloud as bucket type (aws)",
    63  			tmpMpath + "/@aws/bucket/%ob/objname",
    64  			[]string{tmpMpath},
    65  			tmpMpath,
    66  			cmn.Bck{Name: "bucket", Provider: apc.AWS, Ns: cmn.NsGlobal},
    67  			fs.ObjectType, "objname", false,
    68  			false,
    69  		},
    70  		{
    71  			"cloud as bucket type (gcp)",
    72  			tmpMpath + "/@gcp/bucket/%ob/objname",
    73  			[]string{tmpMpath},
    74  			tmpMpath,
    75  			cmn.Bck{Name: "bucket", Provider: apc.GCP, Ns: cmn.NsGlobal},
    76  			fs.ObjectType, "objname", false,
    77  			false,
    78  		},
    79  		{
    80  			"non-empty namespace",
    81  			tmpMpath + "/@ais/#namespace/bucket/%ob/objname",
    82  			[]string{tmpMpath},
    83  			tmpMpath,
    84  			cmn.Bck{Name: "bucket", Provider: apc.AIS, Ns: cmn.Ns{Name: "namespace"}},
    85  			fs.ObjectType, "objname", false,
    86  			false,
    87  		},
    88  		{
    89  			"cloud namespace",
    90  			tmpMpath + "/@ais/@uuid#namespace/bucket/%ob/objname",
    91  			[]string{tmpMpath},
    92  			tmpMpath,
    93  			cmn.Bck{Name: "bucket", Provider: apc.AIS, Ns: cmn.Ns{UUID: "uuid", Name: "namespace"}},
    94  			fs.ObjectType, "objname", false,
    95  			false,
    96  		},
    97  		{
    98  			"long mount path name",
    99  			tmpMpath + "/super/long/@aws/bucket/%ob/objname",
   100  			[]string{tmpMpath + "/super/long"},
   101  			tmpMpath + "/super/long",
   102  			cmn.Bck{Name: "bucket", Provider: apc.AWS, Ns: cmn.NsGlobal},
   103  			fs.ObjectType, "objname", false,
   104  			false,
   105  		},
   106  		{
   107  			"long mount path name and objname in folder",
   108  			tmpMpath + "/super/long/@aws/bucket/%ob/folder/objname",
   109  			[]string{tmpMpath + "/super/long"},
   110  			tmpMpath + "/super/long",
   111  			cmn.Bck{Name: "bucket", Provider: apc.AWS, Ns: cmn.NsGlobal},
   112  			fs.ObjectType, "folder/objname", false,
   113  			false,
   114  		},
   115  
   116  		// bad
   117  		{
   118  			"nested mountpaths",
   119  			tmpMpath + "/super/long/long/@aws/bucket/%ob/folder/objname",
   120  			[]string{"/super/long", "/super/long/long"},
   121  			"",
   122  			cmn.Bck{Name: "bucket", Provider: apc.AWS, Ns: cmn.NsGlobal},
   123  			fs.ObjectType, "folder/objname", true,
   124  			true,
   125  		},
   126  		{
   127  			"too short name",
   128  			tmpMpath + "/bucket/objname",
   129  			[]string{tmpMpath},
   130  			"",
   131  			cmn.Bck{},
   132  			"", "", true,
   133  			false,
   134  		},
   135  		{
   136  			"invalid content type (not prefixed with '%')",
   137  			tmpMpath + "/@gcp/bucket/ob/objname",
   138  			[]string{tmpMpath},
   139  			"",
   140  			cmn.Bck{},
   141  			"", "", true,
   142  			false,
   143  		},
   144  		{
   145  			"invalid content type (empty)",
   146  			tmpMpath + "/@ais/bucket/name",
   147  			[]string{tmpMpath},
   148  			"",
   149  			cmn.Bck{},
   150  			"", "", true,
   151  			false,
   152  		},
   153  		{
   154  			"invalid content type (unknown)",
   155  			tmpMpath + "/@gcp/bucket/%un/objname",
   156  			[]string{tmpMpath},
   157  			"",
   158  			cmn.Bck{},
   159  			"", "", true,
   160  			false,
   161  		},
   162  		{
   163  			"empty bucket name",
   164  			tmpMpath + "/@ais//%ob/objname",
   165  			[]string{tmpMpath},
   166  			"",
   167  			cmn.Bck{},
   168  			"", "", true,
   169  			false,
   170  		},
   171  		{
   172  			"empty bucket name (without slash)",
   173  			tmpMpath + "/@ais/%ob/objname",
   174  			[]string{tmpMpath},
   175  			"",
   176  			cmn.Bck{},
   177  			"", "", true,
   178  			false,
   179  		},
   180  		{
   181  			"empty object name",
   182  			tmpMpath + "/@ais/bucket/%ob/",
   183  			[]string{tmpMpath},
   184  			"",
   185  			cmn.Bck{},
   186  			"", "", true,
   187  			false,
   188  		},
   189  		{
   190  			"empty backend provider",
   191  			tmpMpath + "/bucket/%ob/objname",
   192  			[]string{tmpMpath},
   193  			"",
   194  			cmn.Bck{},
   195  			"", "", true,
   196  			false,
   197  		},
   198  		{
   199  			"invalid backend provider (not prefixed with '@')",
   200  			tmpMpath + "/gcp/bucket/%ob/objname",
   201  			[]string{tmpMpath},
   202  			"",
   203  			cmn.Bck{},
   204  			"", "", true,
   205  			false,
   206  		},
   207  		{
   208  			"invalid backend provider (unknown)",
   209  			tmpMpath + "/@unknown/bucket/%ob/objname",
   210  			[]string{tmpMpath},
   211  			"",
   212  			cmn.Bck{},
   213  			"", "", true,
   214  			false,
   215  		},
   216  		{
   217  			"invalid backend provider (cloud)",
   218  			tmpMpath + "/@cloud/bucket/%ob/objname",
   219  			[]string{tmpMpath},
   220  			"",
   221  			cmn.Bck{},
   222  			"", "", true,
   223  			false,
   224  		},
   225  		{
   226  			"invalid cloud namespace",
   227  			tmpMpath + "/@cloud/@uuid/bucket/%ob/objname",
   228  			[]string{tmpMpath},
   229  			"",
   230  			cmn.Bck{},
   231  			"", "", true,
   232  			false,
   233  		},
   234  		{
   235  			"no matching mountpath",
   236  			tmpMpath + "/@ais/bucket/%obj/objname",
   237  			[]string{tmpMpath + "/a", tmpMpath + "/b"},
   238  			"",
   239  			cmn.Bck{},
   240  			"", "", true,
   241  			false,
   242  		},
   243  		{
   244  			"fqn is mpath",
   245  			tmpMpath + "/mpath",
   246  			[]string{tmpMpath + "/mpath"},
   247  			"",
   248  			cmn.Bck{},
   249  			"", "", true,
   250  			false,
   251  		},
   252  	}
   253  
   254  	for i := range tests {
   255  		tt := tests[i]
   256  		t.Run(tt.testName, func(t *testing.T) {
   257  			mios := mock.NewIOS()
   258  			fs.TestNew(mios)
   259  
   260  			for _, mpath := range tt.mpaths {
   261  				if err := cos.Stat(mpath); os.IsNotExist(err) {
   262  					cos.CreateDir(mpath)
   263  					defer os.RemoveAll(mpath)
   264  				}
   265  				_, err := fs.Add(mpath, "daeID")
   266  				if err != nil && !tt.wantAddErr {
   267  					tassert.CheckFatal(t, err)
   268  				}
   269  			}
   270  			fs.CSM.Reg(fs.ObjectType, &fs.ObjectContentResolver{}, true)
   271  			fs.CSM.Reg(fs.WorkfileType, &fs.WorkfileContentResolver{}, true)
   272  
   273  			var parsed fs.ParsedFQN
   274  			err := parsed.Init(tt.fqn)
   275  			if (err != nil) != tt.wantErr {
   276  				t.Errorf("fqn2info() error = %v, wantErr %v", err, tt.wantErr)
   277  				return
   278  			}
   279  			if err != nil {
   280  				return
   281  			}
   282  			var (
   283  				gotMpath, gotBck           = parsed.Mountpath.Path, parsed.Bck
   284  				gotContentType, gotObjName = parsed.ContentType, parsed.ObjName
   285  			)
   286  			if gotMpath != tt.wantMPath {
   287  				t.Errorf("gotMpath = %v, want %v", gotMpath, tt.wantMPath)
   288  			}
   289  			if !gotBck.Equal(&tt.wantBck) {
   290  				t.Errorf("gotBck = %v, want %v", gotBck, tt.wantBck)
   291  			}
   292  			if gotContentType != tt.wantContentType {
   293  				t.Errorf("gotContentType = %v, want %v", gotContentType, tt.wantContentType)
   294  			}
   295  			if gotObjName != tt.wantObjName {
   296  				t.Errorf("gotObjName = %v, want %v", gotObjName, tt.wantObjName)
   297  			}
   298  		})
   299  	}
   300  }
   301  
   302  func TestMakeAndParseFQN(t *testing.T) {
   303  	tests := []struct {
   304  		mpath       string
   305  		bck         cmn.Bck
   306  		contentType string
   307  		objName     string
   308  	}{
   309  		{
   310  			mpath: "/tmp/path",
   311  			bck: cmn.Bck{
   312  				Name:     "bucket",
   313  				Provider: apc.AIS,
   314  				Ns:       cmn.NsGlobal,
   315  			},
   316  			contentType: fs.ObjectType,
   317  			objName:     "object/name",
   318  		},
   319  		{
   320  			mpath: "/tmp/path",
   321  			bck: cmn.Bck{
   322  				Name:     "bucket",
   323  				Provider: apc.AWS,
   324  				Ns:       cmn.Ns{UUID: "uuid", Name: "namespace"},
   325  			},
   326  			contentType: fs.WorkfileType,
   327  			objName:     "object/name",
   328  		},
   329  		{
   330  			mpath: "/tmp/path",
   331  			bck: cmn.Bck{
   332  				Name:     "bucket",
   333  				Provider: apc.AWS,
   334  				Ns:       cmn.Ns{Name: "alias"},
   335  			},
   336  			contentType: fs.ObjectType,
   337  			objName:     "object/name",
   338  		},
   339  		{
   340  			mpath: "/tmp/path",
   341  			bck: cmn.Bck{
   342  				Name:     "bucket",
   343  				Provider: apc.GCP,
   344  				Ns:       cmn.NsGlobal,
   345  			},
   346  			contentType: fs.ObjectType,
   347  			objName:     "object/name",
   348  		},
   349  	}
   350  
   351  	for i := range tests {
   352  		tt := tests[i]
   353  		t.Run(strings.Join([]string{tt.mpath, tt.bck.String(), tt.contentType, tt.objName}, "|"), func(t *testing.T) {
   354  			mios := mock.NewIOS()
   355  			fs.TestNew(mios)
   356  
   357  			if err := cos.Stat(tt.mpath); os.IsNotExist(err) {
   358  				cos.CreateDir(tt.mpath)
   359  				defer os.RemoveAll(tt.mpath)
   360  			}
   361  			_, err := fs.Add(tt.mpath, "daeID")
   362  			tassert.CheckFatal(t, err)
   363  
   364  			fs.CSM.Reg(fs.ObjectType, &fs.ObjectContentResolver{}, true)
   365  			fs.CSM.Reg(fs.WorkfileType, &fs.WorkfileContentResolver{}, true)
   366  
   367  			mpaths := fs.GetAvail()
   368  			fqn := mpaths[tt.mpath].MakePathFQN(&tt.bck, tt.contentType, tt.objName)
   369  
   370  			var parsed fs.ParsedFQN
   371  			err = parsed.Init(fqn)
   372  			if err != nil {
   373  				t.Fatalf("failed to parse FQN: %v", err)
   374  			}
   375  			var (
   376  				gotMpath, gotBck           = parsed.Mountpath.Path, parsed.Bck
   377  				gotContentType, gotObjName = parsed.ContentType, parsed.ObjName
   378  			)
   379  			if gotMpath != tt.mpath {
   380  				t.Errorf("gotMpath = %v, want %v", gotMpath, tt.mpath)
   381  			}
   382  			if gotBck != tt.bck {
   383  				t.Errorf("gotBck = %v, want %v", gotBck, tt.bck)
   384  			}
   385  			if gotContentType != tt.contentType {
   386  				t.Errorf("getContentType = %v, want %v", gotContentType, tt.contentType)
   387  			}
   388  			if gotObjName != tt.objName {
   389  				t.Errorf("gotObjName = %v, want %v", gotObjName, tt.objName)
   390  			}
   391  		})
   392  	}
   393  }
   394  
   395  func BenchmarkParseFQN(b *testing.B) {
   396  	var (
   397  		mpath = "/tmp/mpath"
   398  		mios  = mock.NewIOS()
   399  		bck   = cmn.Bck{Name: "bucket", Provider: apc.AIS, Ns: cmn.NsGlobal}
   400  	)
   401  
   402  	fs.TestNew(mios)
   403  	cos.CreateDir(mpath)
   404  	defer os.RemoveAll(mpath)
   405  	fs.Add(mpath, "daeID")
   406  	fs.CSM.Reg(fs.ObjectType, &fs.ObjectContentResolver{})
   407  
   408  	mpaths := fs.GetAvail()
   409  	fqn := mpaths[mpath].MakePathFQN(&bck, fs.ObjectType, "super/long/name")
   410  	b.ResetTimer()
   411  
   412  	for range b.N {
   413  		var parsed fs.ParsedFQN
   414  		parsed.Init(fqn)
   415  	}
   416  }