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 }