github.com/pachyderm/pachyderm@v1.13.4/src/server/pfs/fuse/fuse_test.go (about)

     1  package fuse
     2  
     3  import (
     4  	"bytes"
     5  	"crypto/sha256"
     6  	"io/ioutil"
     7  	"math/rand"
     8  	"os"
     9  	"path/filepath"
    10  	"strings"
    11  	"testing"
    12  	"time"
    13  
    14  	"github.com/hanwen/go-fuse/v2/fs"
    15  	"github.com/hanwen/go-fuse/v2/fuse"
    16  
    17  	"github.com/pachyderm/pachyderm/src/client"
    18  	"github.com/pachyderm/pachyderm/src/client/pfs"
    19  	"github.com/pachyderm/pachyderm/src/client/pkg/require"
    20  	"github.com/pachyderm/pachyderm/src/server/pfs/server"
    21  	"github.com/pachyderm/pachyderm/src/server/pkg/workload"
    22  )
    23  
    24  const (
    25  	MB = 1024 * 1024
    26  	GB = 1024 * 1024 * 1024
    27  )
    28  
    29  func TestBasic(t *testing.T) {
    30  	c := server.GetPachClient(t, server.GetBasicConfig())
    31  	require.NoError(t, c.CreateRepo("repo"))
    32  	_, err := c.PutFile("repo", "master", "dir/file1", strings.NewReader("foo"))
    33  	require.NoError(t, err)
    34  	_, err = c.PutFile("repo", "master", "dir/file2", strings.NewReader("foo"))
    35  	require.NoError(t, err)
    36  	withMount(t, c, nil, func(mountPoint string) {
    37  		repos, err := ioutil.ReadDir(mountPoint)
    38  		require.NoError(t, err)
    39  		require.Equal(t, 1, len(repos))
    40  		require.Equal(t, "repo", filepath.Base(repos[0].Name()))
    41  
    42  		files, err := ioutil.ReadDir(filepath.Join(mountPoint, "repo"))
    43  		require.NoError(t, err)
    44  		require.Equal(t, 1, len(files))
    45  		require.Equal(t, "dir", filepath.Base(files[0].Name()))
    46  
    47  		files, err = ioutil.ReadDir(filepath.Join(mountPoint, "repo", "dir"))
    48  		require.NoError(t, err)
    49  		require.Equal(t, 2, len(files))
    50  		require.Equal(t, "file1", filepath.Base(files[0].Name()))
    51  		require.Equal(t, "file2", filepath.Base(files[1].Name()))
    52  
    53  		data, err := ioutil.ReadFile(filepath.Join(mountPoint, "repo", "dir", "file1"))
    54  		require.NoError(t, err)
    55  		require.Equal(t, "foo", string(data))
    56  	})
    57  }
    58  
    59  func TestChunkSize(t *testing.T) {
    60  	c := server.GetPachClient(t, server.GetBasicConfig())
    61  	require.NoError(t, c.CreateRepo("repo"))
    62  	_, err := c.PutFile("repo", "master", "file", strings.NewReader(strings.Repeat("p", int(pfs.ChunkSize))))
    63  	require.NoError(t, err)
    64  	withMount(t, c, nil, func(mountPoint string) {
    65  		data, err := ioutil.ReadFile(filepath.Join(mountPoint, "repo", "file"))
    66  		require.NoError(t, err)
    67  		require.Equal(t, int(pfs.ChunkSize), len(data))
    68  	})
    69  }
    70  
    71  func TestLargeFile(t *testing.T) {
    72  	c := server.GetPachClient(t, server.GetBasicConfig())
    73  	require.NoError(t, c.CreateRepo("repo"))
    74  	src := workload.RandString(rand.New(rand.NewSource(123)), GB+17)
    75  	_, err := c.PutFile("repo", "master", "file", strings.NewReader(src))
    76  	require.NoError(t, err)
    77  	withMount(t, c, nil, func(mountPoint string) {
    78  		data, err := ioutil.ReadFile(filepath.Join(mountPoint, "repo", "file"))
    79  		require.NoError(t, err)
    80  		require.Equal(t, sha256.Sum256([]byte(src)), sha256.Sum256(data))
    81  	})
    82  }
    83  
    84  func BenchmarkLargeFile(b *testing.B) {
    85  	c := server.GetPachClient(b, server.GetBasicConfig())
    86  	require.NoError(b, c.CreateRepo("repo"))
    87  	src := workload.RandString(rand.New(rand.NewSource(123)), GB)
    88  	_, err := c.PutFile("repo", "master", "file", strings.NewReader(src))
    89  	require.NoError(b, err)
    90  	b.ResetTimer()
    91  	for i := 0; i < b.N; i++ {
    92  		withMount(b, c, nil, func(mountPoint string) {
    93  			data, err := ioutil.ReadFile(filepath.Join(mountPoint, "repo", "file"))
    94  			require.NoError(b, err)
    95  			require.Equal(b, GB, len(data))
    96  			b.SetBytes(GB)
    97  		})
    98  	}
    99  }
   100  
   101  func TestSeek(t *testing.T) {
   102  	c := server.GetPachClient(t, server.GetBasicConfig())
   103  	require.NoError(t, c.CreateRepo("repo"))
   104  	data := strings.Repeat("foo", MB)
   105  	_, err := c.PutFile("repo", "master", "file", strings.NewReader(data))
   106  	require.NoError(t, err)
   107  	withMount(t, c, nil, func(mountPoint string) {
   108  		f, err := os.Open(filepath.Join(mountPoint, "repo", "file"))
   109  		require.NoError(t, err)
   110  		defer func() {
   111  			require.NoError(t, f.Close())
   112  		}()
   113  
   114  		testSeek := func(offset int64) {
   115  			_, err = f.Seek(offset, 0)
   116  			require.NoError(t, err)
   117  			d, err := ioutil.ReadAll(f)
   118  			require.NoError(t, err)
   119  			require.Equal(t, data[offset:], string(d))
   120  		}
   121  
   122  		testSeek(0)
   123  		testSeek(MB)
   124  		testSeek(2 * MB)
   125  		testSeek(3 * MB)
   126  	})
   127  }
   128  
   129  func TestHeadlessBranch(t *testing.T) {
   130  	c := server.GetPachClient(t, server.GetBasicConfig())
   131  	require.NoError(t, c.CreateRepo("repo"))
   132  	require.NoError(t, c.CreateBranch("repo", "master", "", nil))
   133  	withMount(t, c, nil, func(mountPoint string) {
   134  		fis, err := ioutil.ReadDir(filepath.Join(mountPoint, "repo"))
   135  		require.NoError(t, err)
   136  		// Headless branches display with 0 files.
   137  		require.Equal(t, 0, len(fis))
   138  	})
   139  }
   140  
   141  func TestReadOnly(t *testing.T) {
   142  	c := server.GetPachClient(t, server.GetBasicConfig())
   143  	require.NoError(t, c.CreateRepo("repo"))
   144  	withMount(t, c, &Options{
   145  		Fuse: &fs.Options{
   146  			MountOptions: fuse.MountOptions{
   147  				Debug: true,
   148  			},
   149  		},
   150  	}, func(mountPoint string) {
   151  		require.YesError(t, ioutil.WriteFile(filepath.Join(mountPoint, "repo", "foo"), []byte("foo\n"), 0644))
   152  	})
   153  }
   154  
   155  func TestWrite(t *testing.T) {
   156  	c := server.GetPachClient(t, server.GetBasicConfig())
   157  	require.NoError(t, c.CreateRepo("repo"))
   158  	// First, create a file
   159  	withMount(t, c, &Options{
   160  		Fuse: &fs.Options{
   161  			MountOptions: fuse.MountOptions{
   162  				Debug: true,
   163  			},
   164  		},
   165  		Write: true,
   166  	}, func(mountPoint string) {
   167  		require.NoError(t, os.MkdirAll(filepath.Join(mountPoint, "repo", "dir"), 0777))
   168  		require.NoError(t, ioutil.WriteFile(filepath.Join(mountPoint, "repo", "dir", "foo"), []byte("foo\n"), 0644))
   169  	})
   170  	var b bytes.Buffer
   171  	require.NoError(t, c.GetFile("repo", "master", "dir/foo", 0, 0, &b))
   172  	require.Equal(t, "foo\n", b.String())
   173  
   174  	// Now append to the file
   175  	withMount(t, c, &Options{
   176  		Fuse: &fs.Options{
   177  			MountOptions: fuse.MountOptions{
   178  				Debug: true,
   179  			},
   180  		},
   181  		Write: true,
   182  	}, func(mountPoint string) {
   183  		data, err := ioutil.ReadFile(filepath.Join(mountPoint, "repo", "dir", "foo"))
   184  		require.NoError(t, err)
   185  		require.Equal(t, "foo\n", string(data))
   186  		f, err := os.OpenFile(filepath.Join(mountPoint, "repo", "dir", "foo"), os.O_WRONLY, 0600)
   187  		require.NoError(t, err)
   188  		defer func() {
   189  			require.NoError(t, f.Close())
   190  		}()
   191  		_, err = f.Seek(0, 2)
   192  		require.NoError(t, err)
   193  		_, err = f.Write([]byte("foo\n"))
   194  		require.NoError(t, err)
   195  	})
   196  	b.Reset()
   197  	require.NoError(t, c.GetFile("repo", "master", "dir/foo", 0, 0, &b))
   198  	require.Equal(t, "foo\nfoo\n", b.String())
   199  
   200  	// Now overwrite that file
   201  	withMount(t, c, &Options{
   202  		Fuse: &fs.Options{
   203  			MountOptions: fuse.MountOptions{
   204  				Debug: true,
   205  			},
   206  		},
   207  		Write: true,
   208  	}, func(mountPoint string) {
   209  		require.NoError(t, os.Remove(filepath.Join(mountPoint, "repo", "dir", "foo")))
   210  		require.NoError(t, ioutil.WriteFile(filepath.Join(mountPoint, "repo", "dir", "foo"), []byte("bar\n"), 0644))
   211  	})
   212  	b.Reset()
   213  	require.NoError(t, c.GetFile("repo", "master", "dir/foo", 0, 0, &b))
   214  	require.Equal(t, "bar\n", b.String())
   215  
   216  	// Now link it to another location
   217  	withMount(t, c, &Options{
   218  		Fuse: &fs.Options{
   219  			MountOptions: fuse.MountOptions{
   220  				Debug: true,
   221  			},
   222  		},
   223  		Write: true,
   224  	}, func(mountPoint string) {
   225  		require.NoError(t, os.Link(filepath.Join(mountPoint, "repo", "dir", "foo"), filepath.Join(mountPoint, "repo", "dir", "bar")))
   226  		require.NoError(t, os.Symlink(filepath.Join(mountPoint, "repo", "dir", "foo"), filepath.Join(mountPoint, "repo", "dir", "buzz")))
   227  	})
   228  	b.Reset()
   229  	require.NoError(t, c.GetFile("repo", "master", "dir/bar", 0, 0, &b))
   230  	require.Equal(t, "bar\n", b.String())
   231  	b.Reset()
   232  	require.NoError(t, c.GetFile("repo", "master", "dir/buzz", 0, 0, &b))
   233  	require.Equal(t, "bar\n", b.String())
   234  
   235  	// Now delete it
   236  	withMount(t, c, &Options{
   237  		Fuse: &fs.Options{
   238  			MountOptions: fuse.MountOptions{
   239  				Debug: true,
   240  			},
   241  		},
   242  		Write: true,
   243  	}, func(mountPoint string) {
   244  		require.NoError(t, os.Remove(filepath.Join(mountPoint, "repo", "dir", "foo")))
   245  	})
   246  	b.Reset()
   247  	require.YesError(t, c.GetFile("repo", "master", "dir/foo", 0, 0, &b))
   248  
   249  	// Try writing to two repos at once
   250  	require.NoError(t, c.CreateRepo("repo2"))
   251  	withMount(t, c, &Options{
   252  		Fuse: &fs.Options{
   253  			MountOptions: fuse.MountOptions{
   254  				Debug: true,
   255  			},
   256  		},
   257  		Write: true,
   258  	}, func(mountPoint string) {
   259  		require.NoError(t, ioutil.WriteFile(filepath.Join(mountPoint, "repo", "file"), []byte("foo\n"), 0644))
   260  		require.NoError(t, ioutil.WriteFile(filepath.Join(mountPoint, "repo2", "file"), []byte("foo\n"), 0644))
   261  	})
   262  }
   263  
   264  func TestRepoOpts(t *testing.T) {
   265  	c := server.GetPachClient(t, server.GetBasicConfig())
   266  	require.NoError(t, c.CreateRepo("repo1"))
   267  	require.NoError(t, c.CreateRepo("repo2"))
   268  	require.NoError(t, c.CreateRepo("repo3"))
   269  	_, err := c.PutFile("repo1", "master", "foo", strings.NewReader("foo\n"))
   270  	require.NoError(t, err)
   271  	withMount(t, c, &Options{
   272  		Fuse: &fs.Options{
   273  			MountOptions: fuse.MountOptions{
   274  				Debug: true,
   275  			},
   276  		},
   277  		RepoOptions: map[string]*RepoOptions{
   278  			"repo1": {},
   279  		},
   280  	}, func(mountPoint string) {
   281  		repos, err := ioutil.ReadDir(mountPoint)
   282  		require.NoError(t, err)
   283  		require.Equal(t, 1, len(repos))
   284  		data, err := ioutil.ReadFile(filepath.Join(mountPoint, "repo1", "foo"))
   285  		require.NoError(t, err)
   286  		require.Equal(t, "foo\n", string(data))
   287  		require.YesError(t, ioutil.WriteFile(filepath.Join(mountPoint, "repo1", "bar"), []byte("bar\n"), 0644))
   288  	})
   289  	withMount(t, c, &Options{
   290  		Fuse: &fs.Options{
   291  			MountOptions: fuse.MountOptions{
   292  				Debug: true,
   293  			},
   294  		},
   295  		RepoOptions: map[string]*RepoOptions{
   296  			"repo1": {Write: true},
   297  		},
   298  	}, func(mountPoint string) {
   299  		repos, err := ioutil.ReadDir(mountPoint)
   300  		require.NoError(t, err)
   301  		require.Equal(t, 1, len(repos))
   302  		data, err := ioutil.ReadFile(filepath.Join(mountPoint, "repo1", "foo"))
   303  		require.NoError(t, err)
   304  		require.Equal(t, "foo\n", string(data))
   305  		require.NoError(t, ioutil.WriteFile(filepath.Join(mountPoint, "repo1", "bar"), []byte("bar\n"), 0644))
   306  	})
   307  
   308  	_, err = c.PutFile("repo1", "staging", "buzz", strings.NewReader("buzz\n"))
   309  	require.NoError(t, err)
   310  	withMount(t, c, &Options{
   311  		Fuse: &fs.Options{
   312  			MountOptions: fuse.MountOptions{
   313  				Debug: true,
   314  			},
   315  		},
   316  		RepoOptions: map[string]*RepoOptions{
   317  			"repo1": {Branch: "staging", Write: true},
   318  		},
   319  	}, func(mountPoint string) {
   320  		repos, err := ioutil.ReadDir(mountPoint)
   321  		require.NoError(t, err)
   322  		require.Equal(t, 1, len(repos))
   323  		_, err = ioutil.ReadFile(filepath.Join(mountPoint, "repo1", "foo"))
   324  		require.YesError(t, err)
   325  		data, err := ioutil.ReadFile(filepath.Join(mountPoint, "repo1", "buzz"))
   326  		require.NoError(t, err)
   327  		require.Equal(t, "buzz\n", string(data))
   328  		require.NoError(t, ioutil.WriteFile(filepath.Join(mountPoint, "repo1", "fizz"), []byte("fizz\n"), 0644))
   329  	})
   330  	var b bytes.Buffer
   331  	require.NoError(t, c.GetFile("repo1", "staging", "fizz", 0, 0, &b))
   332  	require.Equal(t, "fizz\n", b.String())
   333  }
   334  
   335  func TestOpenCommit(t *testing.T) {
   336  	c := server.GetPachClient(t, server.GetBasicConfig())
   337  	require.NoError(t, c.CreateRepo("in"))
   338  	require.NoError(t, c.CreateRepo("out"))
   339  	require.NoError(t, c.CreateBranch("out", "master", "", []*pfs.Branch{client.NewBranch("in", "master")}))
   340  	_, err := c.StartCommit("in", "master")
   341  	require.NoError(t, err)
   342  
   343  	withMount(t, c, &Options{
   344  		Fuse: &fs.Options{
   345  			MountOptions: fuse.MountOptions{
   346  				Debug: true,
   347  			},
   348  		},
   349  		Write: true,
   350  	}, func(mountPoint string) {
   351  		repos, err := ioutil.ReadDir(mountPoint)
   352  		require.NoError(t, err)
   353  		require.Equal(t, 2, len(repos))
   354  		files, err := ioutil.ReadDir(filepath.Join(mountPoint, "in"))
   355  		require.NoError(t, err)
   356  		require.Equal(t, 0, len(files))
   357  		files, err = ioutil.ReadDir(filepath.Join(mountPoint, "out"))
   358  		require.NoError(t, err)
   359  		require.Equal(t, 0, len(files))
   360  	})
   361  }
   362  
   363  func withMount(tb testing.TB, c *client.APIClient, opts *Options, f func(mountPoint string)) {
   364  	dir, err := ioutil.TempDir("", "pfs-mount")
   365  	require.NoError(tb, err)
   366  	defer os.RemoveAll(dir)
   367  	if opts == nil {
   368  		opts = &Options{}
   369  	}
   370  	if opts.Unmount == nil {
   371  		opts.Unmount = make(chan struct{})
   372  	}
   373  	unmounted := make(chan struct{})
   374  	var mountErr error
   375  	defer func() {
   376  		close(opts.Unmount)
   377  		<-unmounted
   378  		require.NoError(tb, mountErr)
   379  	}()
   380  	defer func() {
   381  		// recover because panics leave the mount in a weird state that makes
   382  		// it hard to rerun the tests, mostly relevent when you're iterating on
   383  		// these tests, or the code they test.
   384  		if r := recover(); r != nil {
   385  			tb.Fatal(r)
   386  		}
   387  	}()
   388  	go func() {
   389  		mountErr = Mount(c, dir, opts)
   390  		close(unmounted)
   391  	}()
   392  	// Gotta give the fuse mount time to come up.
   393  	time.Sleep(2 * time.Second)
   394  	f(dir)
   395  }