1 // Copyright (C) The Arvados Authors. All rights reserved.
3 // SPDX-License-Identifier: Apache-2.0
26 check "gopkg.in/check.v1"
29 var _ = check.Suite(&CollectionFSSuite{})
31 type keepClientStub struct {
32 blocks map[string][]byte
33 refreshable map[string]bool
34 onPut func(bufcopy []byte) // called from PutB, before acquiring lock
38 var errStub404 = errors.New("404 block not found")
40 func (kcs *keepClientStub) ReadAt(locator string, p []byte, off int) (int, error) {
43 buf := kcs.blocks[locator[:32]]
47 return copy(p, buf[off:]), nil
50 func (kcs *keepClientStub) PutB(p []byte) (string, int, error) {
51 locator := fmt.Sprintf("%x+%d+A12345@abcde", md5.Sum(p), len(p))
52 buf := make([]byte, len(p))
59 kcs.blocks[locator[:32]] = buf
60 return locator, 1, nil
63 var localOrRemoteSignature = regexp.MustCompile(`\+[AR][^+]*`)
65 func (kcs *keepClientStub) LocalLocator(locator string) (string, error) {
68 if strings.Contains(locator, "+R") {
69 if len(locator) < 32 {
70 return "", fmt.Errorf("bad locator: %q", locator)
72 if _, ok := kcs.blocks[locator[:32]]; !ok && !kcs.refreshable[locator[:32]] {
73 return "", fmt.Errorf("kcs.refreshable[%q]==false", locator)
76 fakeSig := fmt.Sprintf("+A%x@%x", sha1.Sum(nil), time.Now().Add(time.Hour*24*14).Unix())
77 return localOrRemoteSignature.ReplaceAllLiteralString(locator, fakeSig), nil
80 type CollectionFSSuite struct {
83 fs CollectionFileSystem
87 func (s *CollectionFSSuite) SetUpTest(c *check.C) {
88 s.client = NewClientFromEnv()
89 err := s.client.RequestAndDecode(&s.coll, "GET", "arvados/v1/collections/"+fixtureFooAndBarFilesInDirUUID, nil, nil)
90 c.Assert(err, check.IsNil)
91 s.kc = &keepClientStub{
92 blocks: map[string][]byte{
93 "3858f62230ac3c915f300c664312c63f": []byte("foobar"),
95 s.fs, err = s.coll.FileSystem(s.client, s.kc)
96 c.Assert(err, check.IsNil)
99 func (s *CollectionFSSuite) TestHttpFileSystemInterface(c *check.C) {
100 _, ok := s.fs.(http.FileSystem)
101 c.Check(ok, check.Equals, true)
104 func (s *CollectionFSSuite) TestColonInFilename(c *check.C) {
105 fs, err := (&Collection{
106 ManifestText: "./foo:foo 3858f62230ac3c915f300c664312c63f+3 0:3:bar:bar\n",
107 }).FileSystem(s.client, s.kc)
108 c.Assert(err, check.IsNil)
110 f, err := fs.Open("/foo:foo")
111 c.Assert(err, check.IsNil)
113 fis, err := f.Readdir(0)
114 c.Check(err, check.IsNil)
115 c.Check(len(fis), check.Equals, 1)
116 c.Check(fis[0].Name(), check.Equals, "bar:bar")
119 func (s *CollectionFSSuite) TestReaddirFull(c *check.C) {
120 f, err := s.fs.Open("/dir1")
121 c.Assert(err, check.IsNil)
124 c.Assert(err, check.IsNil)
125 c.Check(st.Size(), check.Equals, int64(2))
126 c.Check(st.IsDir(), check.Equals, true)
128 fis, err := f.Readdir(0)
129 c.Check(err, check.IsNil)
130 c.Check(len(fis), check.Equals, 2)
132 c.Check(fis[0].Size(), check.Equals, int64(3))
136 func (s *CollectionFSSuite) TestReaddirLimited(c *check.C) {
137 f, err := s.fs.Open("./dir1")
138 c.Assert(err, check.IsNil)
140 fis, err := f.Readdir(1)
141 c.Check(err, check.IsNil)
142 c.Check(len(fis), check.Equals, 1)
144 c.Check(fis[0].Size(), check.Equals, int64(3))
147 fis, err = f.Readdir(1)
148 c.Check(err, check.IsNil)
149 c.Check(len(fis), check.Equals, 1)
151 c.Check(fis[0].Size(), check.Equals, int64(3))
154 fis, err = f.Readdir(1)
155 c.Check(len(fis), check.Equals, 0)
156 c.Check(err, check.NotNil)
157 c.Check(err, check.Equals, io.EOF)
159 f, err = s.fs.Open("dir1")
160 c.Assert(err, check.IsNil)
161 fis, err = f.Readdir(1)
162 c.Check(len(fis), check.Equals, 1)
163 c.Assert(err, check.IsNil)
164 fis, err = f.Readdir(2)
165 c.Check(len(fis), check.Equals, 1)
166 c.Assert(err, check.IsNil)
167 fis, err = f.Readdir(2)
168 c.Check(len(fis), check.Equals, 0)
169 c.Assert(err, check.Equals, io.EOF)
172 func (s *CollectionFSSuite) TestPathMunge(c *check.C) {
173 for _, path := range []string{".", "/", "./", "///", "/../", "/./.."} {
174 f, err := s.fs.Open(path)
175 c.Assert(err, check.IsNil)
178 c.Assert(err, check.IsNil)
179 c.Check(st.Size(), check.Equals, int64(1))
180 c.Check(st.IsDir(), check.Equals, true)
182 for _, path := range []string{"/dir1", "dir1", "./dir1", "///dir1//.//", "../dir1/../dir1/"} {
184 f, err := s.fs.Open(path)
185 c.Assert(err, check.IsNil)
188 c.Assert(err, check.IsNil)
189 c.Check(st.Size(), check.Equals, int64(2))
190 c.Check(st.IsDir(), check.Equals, true)
194 func (s *CollectionFSSuite) TestNotExist(c *check.C) {
195 for _, path := range []string{"/no", "no", "./no", "n/o", "/n/o"} {
196 f, err := s.fs.Open(path)
197 c.Assert(f, check.IsNil)
198 c.Assert(err, check.NotNil)
199 c.Assert(os.IsNotExist(err), check.Equals, true)
203 func (s *CollectionFSSuite) TestReadOnlyFile(c *check.C) {
204 f, err := s.fs.OpenFile("/dir1/foo", os.O_RDONLY, 0)
205 c.Assert(err, check.IsNil)
207 c.Assert(err, check.IsNil)
208 c.Check(st.Size(), check.Equals, int64(3))
209 n, err := f.Write([]byte("bar"))
210 c.Check(n, check.Equals, 0)
211 c.Check(err, check.Equals, ErrReadOnlyFile)
214 func (s *CollectionFSSuite) TestCreateFile(c *check.C) {
215 f, err := s.fs.OpenFile("/new-file 1", os.O_RDWR|os.O_CREATE, 0)
216 c.Assert(err, check.IsNil)
218 c.Assert(err, check.IsNil)
219 c.Check(st.Size(), check.Equals, int64(0))
221 n, err := f.Write([]byte("bar"))
222 c.Check(n, check.Equals, 3)
223 c.Check(err, check.IsNil)
225 c.Check(f.Close(), check.IsNil)
227 f, err = s.fs.OpenFile("/new-file 1", os.O_RDWR|os.O_CREATE|os.O_EXCL, 0)
228 c.Check(f, check.IsNil)
229 c.Assert(err, check.NotNil)
231 f, err = s.fs.OpenFile("/new-file 1", os.O_RDWR, 0)
232 c.Assert(err, check.IsNil)
234 c.Assert(err, check.IsNil)
235 c.Check(st.Size(), check.Equals, int64(3))
237 c.Check(f.Close(), check.IsNil)
239 m, err := s.fs.MarshalManifest(".")
240 c.Assert(err, check.IsNil)
241 c.Check(m, check.Matches, `. 37b51d194a7513e45b56f6524f2d51f2\+3\+\S+ 0:3:new-file\\0401\n./dir1 .* 3:3:bar 0:3:foo\n`)
244 func (s *CollectionFSSuite) TestReadWriteFile(c *check.C) {
246 defer func() { maxBlockSize = 2 << 26 }()
248 f, err := s.fs.OpenFile("/dir1/foo", os.O_RDWR, 0)
249 c.Assert(err, check.IsNil)
252 c.Assert(err, check.IsNil)
253 c.Check(st.Size(), check.Equals, int64(3))
255 f2, err := s.fs.OpenFile("/dir1/foo", os.O_RDWR, 0)
256 c.Assert(err, check.IsNil)
259 buf := make([]byte, 64)
260 n, err := f.Read(buf)
261 c.Check(n, check.Equals, 3)
262 c.Check(err, check.Equals, io.EOF)
263 c.Check(string(buf[:3]), check.DeepEquals, "foo")
265 pos, err := f.Seek(-2, io.SeekCurrent)
266 c.Check(pos, check.Equals, int64(1))
267 c.Check(err, check.IsNil)
269 // Split a storedExtent in two, and insert a memExtent
270 n, err = f.Write([]byte("*"))
271 c.Check(n, check.Equals, 1)
272 c.Check(err, check.IsNil)
274 pos, err = f.Seek(0, io.SeekCurrent)
275 c.Check(pos, check.Equals, int64(2))
276 c.Check(err, check.IsNil)
278 pos, err = f.Seek(0, io.SeekStart)
279 c.Check(pos, check.Equals, int64(0))
280 c.Check(err, check.IsNil)
282 rbuf, err := ioutil.ReadAll(f)
283 c.Check(len(rbuf), check.Equals, 3)
284 c.Check(err, check.IsNil)
285 c.Check(string(rbuf), check.Equals, "f*o")
287 // Write multiple blocks in one call
288 f.Seek(1, io.SeekStart)
289 n, err = f.Write([]byte("0123456789abcdefg"))
290 c.Check(n, check.Equals, 17)
291 c.Check(err, check.IsNil)
292 pos, err = f.Seek(0, io.SeekCurrent)
293 c.Check(pos, check.Equals, int64(18))
294 c.Check(err, check.IsNil)
295 pos, err = f.Seek(-18, io.SeekCurrent)
296 c.Check(pos, check.Equals, int64(0))
297 c.Check(err, check.IsNil)
298 n, err = io.ReadFull(f, buf)
299 c.Check(n, check.Equals, 18)
300 c.Check(err, check.Equals, io.ErrUnexpectedEOF)
301 c.Check(string(buf[:n]), check.Equals, "f0123456789abcdefg")
303 buf2, err := ioutil.ReadAll(f2)
304 c.Check(err, check.IsNil)
305 c.Check(string(buf2), check.Equals, "f0123456789abcdefg")
307 // truncate to current size
309 c.Check(err, check.IsNil)
310 f2.Seek(0, io.SeekStart)
311 buf2, err = ioutil.ReadAll(f2)
312 c.Check(err, check.IsNil)
313 c.Check(string(buf2), check.Equals, "f0123456789abcdefg")
315 // shrink to zero some data
317 f2.Seek(0, io.SeekStart)
318 buf2, err = ioutil.ReadAll(f2)
319 c.Check(err, check.IsNil)
320 c.Check(string(buf2), check.Equals, "f0123456789abcd")
322 // grow to partial block/extent
324 f2.Seek(0, io.SeekStart)
325 buf2, err = ioutil.ReadAll(f2)
326 c.Check(err, check.IsNil)
327 c.Check(string(buf2), check.Equals, "f0123456789abcd\x00\x00\x00\x00\x00")
330 f2.Seek(0, io.SeekStart)
331 f2.Write([]byte("12345678abcdefghijkl"))
333 // grow to block/extent boundary
335 f2.Seek(0, io.SeekStart)
336 buf2, err = ioutil.ReadAll(f2)
337 c.Check(err, check.IsNil)
338 c.Check(len(buf2), check.Equals, 64)
339 c.Check(len(f.(*filehandle).inode.(*filenode).segments), check.Equals, 8)
341 // shrink to block/extent boundary
343 c.Check(err, check.IsNil)
344 f2.Seek(0, io.SeekStart)
345 buf2, err = ioutil.ReadAll(f2)
346 c.Check(err, check.IsNil)
347 c.Check(len(buf2), check.Equals, 32)
348 c.Check(len(f.(*filehandle).inode.(*filenode).segments), check.Equals, 4)
350 // shrink to partial block/extent
352 c.Check(err, check.IsNil)
353 f2.Seek(0, io.SeekStart)
354 buf2, err = ioutil.ReadAll(f2)
355 c.Check(err, check.IsNil)
356 c.Check(string(buf2), check.Equals, "12345678abcdefg")
357 c.Check(len(f.(*filehandle).inode.(*filenode).segments), check.Equals, 2)
359 // Force flush to ensure the block "12345678" gets stored, so
360 // we know what to expect in the final manifest below.
361 _, err = s.fs.MarshalManifest(".")
362 c.Check(err, check.IsNil)
364 // Truncate to size=3 while f2's ptr is at 15
366 c.Check(err, check.IsNil)
367 buf2, err = ioutil.ReadAll(f2)
368 c.Check(err, check.IsNil)
369 c.Check(string(buf2), check.Equals, "")
370 f2.Seek(0, io.SeekStart)
371 buf2, err = ioutil.ReadAll(f2)
372 c.Check(err, check.IsNil)
373 c.Check(string(buf2), check.Equals, "123")
374 c.Check(len(f.(*filehandle).inode.(*filenode).segments), check.Equals, 1)
376 m, err := s.fs.MarshalManifest(".")
377 c.Check(err, check.IsNil)
378 m = regexp.MustCompile(`\+A[^\+ ]+`).ReplaceAllLiteralString(m, "")
379 c.Check(m, check.Equals, "./dir1 3858f62230ac3c915f300c664312c63f+6 25d55ad283aa400af464c76d713c07ad+8 3:3:bar 6:3:foo\n")
380 c.Check(s.fs.Size(), check.Equals, int64(6))
383 func (s *CollectionFSSuite) TestSeekSparse(c *check.C) {
384 fs, err := (&Collection{}).FileSystem(s.client, s.kc)
385 c.Assert(err, check.IsNil)
386 f, err := fs.OpenFile("test", os.O_CREATE|os.O_RDWR, 0755)
387 c.Assert(err, check.IsNil)
390 checkSize := func(size int64) {
392 c.Assert(err, check.IsNil)
393 c.Check(fi.Size(), check.Equals, size)
395 f, err := fs.OpenFile("test", os.O_CREATE|os.O_RDWR, 0755)
396 c.Assert(err, check.IsNil)
399 c.Check(err, check.IsNil)
400 c.Check(fi.Size(), check.Equals, size)
401 pos, err := f.Seek(0, io.SeekEnd)
402 c.Check(err, check.IsNil)
403 c.Check(pos, check.Equals, size)
406 f.Seek(2, io.SeekEnd)
411 f.Seek(2, io.SeekCurrent)
416 f.Seek(8, io.SeekStart)
418 n, err := f.Read(make([]byte, 1))
419 c.Check(n, check.Equals, 0)
420 c.Check(err, check.Equals, io.EOF)
422 f.Write([]byte{1, 2, 3})
426 func (s *CollectionFSSuite) TestMarshalCopiesRemoteBlocks(c *check.C) {
429 hash := map[string]string{
430 foo: fmt.Sprintf("%x", md5.Sum([]byte(foo))),
431 bar: fmt.Sprintf("%x", md5.Sum([]byte(bar))),
434 fs, err := (&Collection{
435 ManifestText: ". " + hash[foo] + "+3+Rzaaaa-foo@bab " + hash[bar] + "+3+A12345@ffffff 0:2:fo.txt 2:4:obar.txt\n",
436 }).FileSystem(s.client, s.kc)
437 c.Assert(err, check.IsNil)
438 manifest, err := fs.MarshalManifest(".")
439 c.Check(manifest, check.Equals, "")
440 c.Check(err, check.NotNil)
442 s.kc.refreshable = map[string]bool{hash[bar]: true}
444 for _, sigIn := range []string{"Rzaaaa-foo@bab", "A12345@abcde"} {
445 fs, err = (&Collection{
446 ManifestText: ". " + hash[foo] + "+3+A12345@fffff " + hash[bar] + "+3+" + sigIn + " 0:2:fo.txt 2:4:obar.txt\n",
447 }).FileSystem(s.client, s.kc)
448 c.Assert(err, check.IsNil)
449 manifest, err := fs.MarshalManifest(".")
450 c.Check(err, check.IsNil)
451 // Both blocks should now have +A signatures.
452 c.Check(manifest, check.Matches, `.*\+A.* .*\+A.*\n`)
453 c.Check(manifest, check.Not(check.Matches), `.*\+R.*\n`)
457 func (s *CollectionFSSuite) TestMarshalSmallBlocks(c *check.C) {
459 defer func() { maxBlockSize = 2 << 26 }()
462 s.fs, err = (&Collection{}).FileSystem(s.client, s.kc)
463 c.Assert(err, check.IsNil)
464 for _, name := range []string{"foo", "bar", "baz"} {
465 f, err := s.fs.OpenFile(name, os.O_WRONLY|os.O_CREATE, 0)
466 c.Assert(err, check.IsNil)
467 f.Write([]byte(name))
471 m, err := s.fs.MarshalManifest(".")
472 c.Check(err, check.IsNil)
473 m = regexp.MustCompile(`\+A[^\+ ]+`).ReplaceAllLiteralString(m, "")
474 c.Check(m, check.Equals, ". c3c23db5285662ef7172373df0003206+6 acbd18db4cc2f85cedef654fccc4a4d8+3 0:3:bar 3:3:baz 6:3:foo\n")
477 func (s *CollectionFSSuite) TestMkdir(c *check.C) {
478 err := s.fs.Mkdir("foo/bar", 0755)
479 c.Check(err, check.Equals, os.ErrNotExist)
481 f, err := s.fs.OpenFile("foo/bar", os.O_CREATE, 0)
482 c.Check(err, check.Equals, os.ErrNotExist)
484 err = s.fs.Mkdir("foo", 0755)
485 c.Check(err, check.IsNil)
487 f, err = s.fs.OpenFile("foo/bar", os.O_CREATE|os.O_WRONLY, 0)
488 c.Check(err, check.IsNil)
491 f.Write([]byte("foo"))
494 // mkdir fails if a file already exists with that name
495 err = s.fs.Mkdir("foo/bar", 0755)
496 c.Check(err, check.NotNil)
498 err = s.fs.Remove("foo/bar")
499 c.Check(err, check.IsNil)
501 // mkdir succeeds after the file is deleted
502 err = s.fs.Mkdir("foo/bar", 0755)
503 c.Check(err, check.IsNil)
505 // creating a file in a nonexistent subdir should still fail
506 f, err = s.fs.OpenFile("foo/bar/baz/foo.txt", os.O_CREATE|os.O_WRONLY, 0)
507 c.Check(err, check.Equals, os.ErrNotExist)
509 f, err = s.fs.OpenFile("foo/bar/foo.txt", os.O_CREATE|os.O_WRONLY, 0)
510 c.Check(err, check.IsNil)
513 f.Write([]byte("foo"))
516 // creating foo/bar as a regular file should fail
517 f, err = s.fs.OpenFile("foo/bar", os.O_CREATE|os.O_EXCL, 0)
518 c.Check(err, check.NotNil)
520 // creating foo/bar as a directory should fail
521 f, err = s.fs.OpenFile("foo/bar", os.O_CREATE|os.O_EXCL, os.ModeDir)
522 c.Check(err, check.NotNil)
523 err = s.fs.Mkdir("foo/bar", 0755)
524 c.Check(err, check.NotNil)
526 m, err := s.fs.MarshalManifest(".")
527 c.Check(err, check.IsNil)
528 m = regexp.MustCompile(`\+A[^\+ ]+`).ReplaceAllLiteralString(m, "")
529 c.Check(m, check.Equals, "./dir1 3858f62230ac3c915f300c664312c63f+6 3:3:bar 0:3:foo\n./foo/bar acbd18db4cc2f85cedef654fccc4a4d8+3 0:3:foo.txt\n")
532 func (s *CollectionFSSuite) TestConcurrentWriters(c *check.C) {
538 defer func() { maxBlockSize = 2 << 26 }()
540 var wg sync.WaitGroup
541 for n := 0; n < 128; n++ {
545 f, err := s.fs.OpenFile("/dir1/foo", os.O_RDWR, 0)
546 c.Assert(err, check.IsNil)
548 for i := 0; i < 1024; i++ {
552 _, err := s.fs.MarshalManifest(".")
553 c.Check(err, check.IsNil)
555 f.Truncate(int64(rand.Intn(64)))
557 f.Seek(int64(rand.Intn(64)), io.SeekStart)
559 _, err := f.Write([]byte("beep boop"))
560 c.Check(err, check.IsNil)
562 _, err := ioutil.ReadAll(f)
563 c.Check(err, check.IsNil)
570 f, err := s.fs.OpenFile("/dir1/foo", os.O_RDWR, 0)
571 c.Assert(err, check.IsNil)
573 buf, err := ioutil.ReadAll(f)
574 c.Check(err, check.IsNil)
575 c.Logf("after lots of random r/w/seek/trunc, buf is %q", buf)
578 func (s *CollectionFSSuite) TestRandomWrites(c *check.C) {
580 defer func() { maxBlockSize = 2 << 26 }()
583 s.fs, err = (&Collection{}).FileSystem(s.client, s.kc)
584 c.Assert(err, check.IsNil)
587 const ngoroutines = 256
589 var wg sync.WaitGroup
590 for n := 0; n < ngoroutines; n++ {
594 expect := make([]byte, 0, 64)
595 wbytes := []byte("there's no simple explanation for anything important that any of us do")
596 f, err := s.fs.OpenFile(fmt.Sprintf("random-%d", n), os.O_RDWR|os.O_CREATE|os.O_EXCL, 0)
597 c.Assert(err, check.IsNil)
599 for i := 0; i < nfiles; i++ {
600 trunc := rand.Intn(65)
601 woff := rand.Intn(trunc + 1)
602 wbytes = wbytes[:rand.Intn(64-woff+1)]
603 for buf, i := expect[:cap(expect)], len(expect); i < trunc; i++ {
606 expect = expect[:trunc]
607 if trunc < woff+len(wbytes) {
608 expect = expect[:woff+len(wbytes)]
610 copy(expect[woff:], wbytes)
611 f.Truncate(int64(trunc))
612 pos, err := f.Seek(int64(woff), io.SeekStart)
613 c.Check(pos, check.Equals, int64(woff))
614 c.Check(err, check.IsNil)
615 n, err := f.Write(wbytes)
616 c.Check(n, check.Equals, len(wbytes))
617 c.Check(err, check.IsNil)
618 pos, err = f.Seek(0, io.SeekStart)
619 c.Check(pos, check.Equals, int64(0))
620 c.Check(err, check.IsNil)
621 buf, err := ioutil.ReadAll(f)
622 c.Check(string(buf), check.Equals, string(expect))
623 c.Check(err, check.IsNil)
629 for n := 0; n < ngoroutines; n++ {
630 f, err := s.fs.OpenFile(fmt.Sprintf("random-%d", n), os.O_RDONLY, 0)
631 c.Assert(err, check.IsNil)
632 f.(*filehandle).inode.(*filenode).waitPrune()
637 root, err := s.fs.Open("/")
638 c.Assert(err, check.IsNil)
640 fi, err := root.Readdir(-1)
641 c.Check(err, check.IsNil)
642 c.Check(len(fi), check.Equals, nfiles)
644 _, err = s.fs.MarshalManifest(".")
645 c.Check(err, check.IsNil)
646 // TODO: check manifest content
649 func (s *CollectionFSSuite) TestRemove(c *check.C) {
650 fs, err := (&Collection{}).FileSystem(s.client, s.kc)
651 c.Assert(err, check.IsNil)
652 err = fs.Mkdir("dir0", 0755)
653 c.Assert(err, check.IsNil)
654 err = fs.Mkdir("dir1", 0755)
655 c.Assert(err, check.IsNil)
656 err = fs.Mkdir("dir1/dir2", 0755)
657 c.Assert(err, check.IsNil)
658 err = fs.Mkdir("dir1/dir3", 0755)
659 c.Assert(err, check.IsNil)
661 err = fs.Remove("dir0")
662 c.Check(err, check.IsNil)
663 err = fs.Remove("dir0")
664 c.Check(err, check.Equals, os.ErrNotExist)
666 err = fs.Remove("dir1/dir2/.")
667 c.Check(err, check.Equals, ErrInvalidArgument)
668 err = fs.Remove("dir1/dir2/..")
669 c.Check(err, check.Equals, ErrInvalidArgument)
670 err = fs.Remove("dir1")
671 c.Check(err, check.Equals, ErrDirectoryNotEmpty)
672 err = fs.Remove("dir1/dir2/../../../dir1")
673 c.Check(err, check.Equals, ErrDirectoryNotEmpty)
674 err = fs.Remove("dir1/dir3/")
675 c.Check(err, check.IsNil)
676 err = fs.RemoveAll("dir1")
677 c.Check(err, check.IsNil)
678 err = fs.RemoveAll("dir1")
679 c.Check(err, check.IsNil)
682 func (s *CollectionFSSuite) TestRenameError(c *check.C) {
683 fs, err := (&Collection{}).FileSystem(s.client, s.kc)
684 c.Assert(err, check.IsNil)
685 err = fs.Mkdir("first", 0755)
686 c.Assert(err, check.IsNil)
687 err = fs.Mkdir("first/second", 0755)
688 c.Assert(err, check.IsNil)
689 f, err := fs.OpenFile("first/second/file", os.O_CREATE|os.O_WRONLY, 0755)
690 c.Assert(err, check.IsNil)
691 f.Write([]byte{1, 2, 3, 4, 5})
693 err = fs.Rename("first", "first/second/third")
694 c.Check(err, check.Equals, ErrInvalidArgument)
695 err = fs.Rename("first", "first/third")
696 c.Check(err, check.Equals, ErrInvalidArgument)
697 err = fs.Rename("first/second", "second")
698 c.Check(err, check.IsNil)
699 f, err = fs.OpenFile("second/file", 0, 0)
700 c.Assert(err, check.IsNil)
701 data, err := ioutil.ReadAll(f)
702 c.Check(err, check.IsNil)
703 c.Check(data, check.DeepEquals, []byte{1, 2, 3, 4, 5})
706 func (s *CollectionFSSuite) TestRenameDirectory(c *check.C) {
707 fs, err := (&Collection{}).FileSystem(s.client, s.kc)
708 c.Assert(err, check.IsNil)
709 err = fs.Mkdir("foo", 0755)
710 c.Assert(err, check.IsNil)
711 err = fs.Mkdir("bar", 0755)
712 c.Assert(err, check.IsNil)
713 err = fs.Rename("bar", "baz")
714 c.Check(err, check.IsNil)
715 err = fs.Rename("foo", "baz")
716 c.Check(err, check.NotNil)
717 err = fs.Rename("foo", "baz/")
718 c.Check(err, check.IsNil)
719 err = fs.Rename("baz/foo", ".")
720 c.Check(err, check.Equals, ErrInvalidArgument)
721 err = fs.Rename("baz/foo/", ".")
722 c.Check(err, check.Equals, ErrInvalidArgument)
725 func (s *CollectionFSSuite) TestRename(c *check.C) {
726 fs, err := (&Collection{}).FileSystem(s.client, s.kc)
727 c.Assert(err, check.IsNil)
732 for i := 0; i < outer; i++ {
733 err = fs.Mkdir(fmt.Sprintf("dir%d", i), 0755)
734 c.Assert(err, check.IsNil)
735 for j := 0; j < inner; j++ {
736 err = fs.Mkdir(fmt.Sprintf("dir%d/dir%d", i, j), 0755)
737 c.Assert(err, check.IsNil)
738 for _, fnm := range []string{
739 fmt.Sprintf("dir%d/file%d", i, j),
740 fmt.Sprintf("dir%d/dir%d/file%d", i, j, j),
742 f, err := fs.OpenFile(fnm, os.O_CREATE|os.O_WRONLY, 0755)
743 c.Assert(err, check.IsNil)
744 _, err = f.Write([]byte("beep"))
745 c.Assert(err, check.IsNil)
750 var wg sync.WaitGroup
751 for i := 0; i < outer; i++ {
752 for j := 0; j < inner; j++ {
756 oldname := fmt.Sprintf("dir%d/dir%d/file%d", i, j, j)
757 newname := fmt.Sprintf("dir%d/newfile%d", i, inner-j-1)
758 _, err := fs.Open(newname)
759 c.Check(err, check.Equals, os.ErrNotExist)
760 err = fs.Rename(oldname, newname)
761 c.Check(err, check.IsNil)
762 f, err := fs.Open(newname)
763 c.Check(err, check.IsNil)
770 // oldname does not exist
772 fmt.Sprintf("dir%d/dir%d/missing", i, j),
773 fmt.Sprintf("dir%d/dir%d/file%d", outer-i-1, j, j))
774 c.Check(err, check.ErrorMatches, `.*does not exist`)
776 // newname parent dir does not exist
778 fmt.Sprintf("dir%d/dir%d", i, j),
779 fmt.Sprintf("dir%d/missing/irrelevant", outer-i-1))
780 c.Check(err, check.ErrorMatches, `.*does not exist`)
782 // oldname parent dir is a file
784 fmt.Sprintf("dir%d/file%d/patherror", i, j),
785 fmt.Sprintf("dir%d/irrelevant", i))
786 c.Check(err, check.ErrorMatches, `.*not a directory`)
788 // newname parent dir is a file
790 fmt.Sprintf("dir%d/dir%d/file%d", i, j, j),
791 fmt.Sprintf("dir%d/file%d/patherror", i, inner-j-1))
792 c.Check(err, check.ErrorMatches, `.*not a directory`)
798 f, err := fs.OpenFile("dir1/newfile3", 0, 0)
799 c.Assert(err, check.IsNil)
800 c.Check(f.Size(), check.Equals, int64(4))
801 buf, err := ioutil.ReadAll(f)
802 c.Check(buf, check.DeepEquals, []byte("beep"))
803 c.Check(err, check.IsNil)
804 _, err = fs.Open("dir1/dir1/file1")
805 c.Check(err, check.Equals, os.ErrNotExist)
808 func (s *CollectionFSSuite) TestPersist(c *check.C) {
810 defer func() { maxBlockSize = 2 << 26 }()
813 s.fs, err = (&Collection{}).FileSystem(s.client, s.kc)
814 c.Assert(err, check.IsNil)
815 err = s.fs.Mkdir("d:r", 0755)
816 c.Assert(err, check.IsNil)
818 expect := map[string][]byte{}
820 var wg sync.WaitGroup
821 for _, name := range []string{"random 1", "random:2", "random\\3", "d:r/random4"} {
822 buf := make([]byte, 500)
826 f, err := s.fs.OpenFile(name, os.O_WRONLY|os.O_CREATE, 0)
827 c.Assert(err, check.IsNil)
828 // Note: we don't close the file until after the test
829 // is done. Writes to unclosed files should persist.
835 for i := 0; i < len(buf); i += 5 {
836 _, err := f.Write(buf[i : i+5])
837 c.Assert(err, check.IsNil)
843 m, err := s.fs.MarshalManifest(".")
844 c.Check(err, check.IsNil)
847 root, err := s.fs.Open("/")
848 c.Assert(err, check.IsNil)
850 fi, err := root.Readdir(-1)
851 c.Check(err, check.IsNil)
852 c.Check(len(fi), check.Equals, 4)
854 persisted, err := (&Collection{ManifestText: m}).FileSystem(s.client, s.kc)
855 c.Assert(err, check.IsNil)
857 root, err = persisted.Open("/")
858 c.Assert(err, check.IsNil)
860 fi, err = root.Readdir(-1)
861 c.Check(err, check.IsNil)
862 c.Check(len(fi), check.Equals, 4)
864 for name, content := range expect {
865 c.Logf("read %q", name)
866 f, err := persisted.Open(name)
867 c.Assert(err, check.IsNil)
869 buf, err := ioutil.ReadAll(f)
870 c.Check(err, check.IsNil)
871 c.Check(buf, check.DeepEquals, content)
875 func (s *CollectionFSSuite) TestPersistEmptyFilesAndDirs(c *check.C) {
877 s.fs, err = (&Collection{}).FileSystem(s.client, s.kc)
878 c.Assert(err, check.IsNil)
879 for _, name := range []string{"dir", "dir/zerodir", "empty", "not empty", "not empty/empty", "zero", "zero/zero"} {
880 err = s.fs.Mkdir(name, 0755)
881 c.Assert(err, check.IsNil)
884 expect := map[string][]byte{
891 "dir/zerodir/zero": nil,
892 "zero/zero/zero": nil,
894 for name, data := range expect {
895 f, err := s.fs.OpenFile(name, os.O_WRONLY|os.O_CREATE, 0)
896 c.Assert(err, check.IsNil)
898 _, err := f.Write(data)
899 c.Assert(err, check.IsNil)
904 m, err := s.fs.MarshalManifest(".")
905 c.Check(err, check.IsNil)
908 persisted, err := (&Collection{ManifestText: m}).FileSystem(s.client, s.kc)
909 c.Assert(err, check.IsNil)
911 for name, data := range expect {
912 _, err = persisted.Open("bogus-" + name)
913 c.Check(err, check.NotNil)
915 f, err := persisted.Open(name)
916 c.Assert(err, check.IsNil)
921 buf, err := ioutil.ReadAll(f)
922 c.Check(err, check.IsNil)
923 c.Check(buf, check.DeepEquals, data)
926 expectDir := map[string]int{
929 "not empty/empty": 0,
931 for name, expectLen := range expectDir {
932 _, err := persisted.Open(name + "/bogus")
933 c.Check(err, check.NotNil)
935 d, err := persisted.Open(name)
937 c.Check(err, check.IsNil)
938 fi, err := d.Readdir(-1)
939 c.Check(err, check.IsNil)
940 c.Check(fi, check.HasLen, expectLen)
944 func (s *CollectionFSSuite) TestOpenFileFlags(c *check.C) {
945 fs, err := (&Collection{}).FileSystem(s.client, s.kc)
946 c.Assert(err, check.IsNil)
948 f, err := fs.OpenFile("missing", os.O_WRONLY, 0)
949 c.Check(f, check.IsNil)
950 c.Check(err, check.ErrorMatches, `file does not exist`)
952 f, err = fs.OpenFile("new", os.O_CREATE|os.O_RDONLY, 0)
953 c.Assert(err, check.IsNil)
955 n, err := f.Write([]byte{1, 2, 3})
956 c.Check(n, check.Equals, 0)
957 c.Check(err, check.ErrorMatches, `read-only file`)
958 n, err = f.Read(make([]byte, 1))
959 c.Check(n, check.Equals, 0)
960 c.Check(err, check.Equals, io.EOF)
961 f, err = fs.OpenFile("new", os.O_RDWR, 0)
962 c.Assert(err, check.IsNil)
964 _, err = f.Write([]byte{4, 5, 6})
965 c.Check(err, check.IsNil)
967 c.Assert(err, check.IsNil)
968 c.Check(fi.Size(), check.Equals, int64(3))
970 f, err = fs.OpenFile("new", os.O_TRUNC|os.O_RDWR, 0)
971 c.Assert(err, check.IsNil)
973 pos, err := f.Seek(0, io.SeekEnd)
974 c.Check(pos, check.Equals, int64(0))
975 c.Check(err, check.IsNil)
977 c.Assert(err, check.IsNil)
978 c.Check(fi.Size(), check.Equals, int64(0))
981 buf := make([]byte, 64)
982 f, err = fs.OpenFile("append", os.O_EXCL|os.O_CREATE|os.O_RDWR|os.O_APPEND, 0)
983 c.Assert(err, check.IsNil)
984 f.Write([]byte{1, 2, 3})
985 f.Seek(0, io.SeekStart)
986 n, _ = f.Read(buf[:1])
987 c.Check(n, check.Equals, 1)
988 c.Check(buf[:1], check.DeepEquals, []byte{1})
989 pos, err = f.Seek(0, io.SeekCurrent)
990 c.Assert(err, check.IsNil)
991 c.Check(pos, check.Equals, int64(1))
992 f.Write([]byte{4, 5, 6})
993 pos, err = f.Seek(0, io.SeekCurrent)
994 c.Assert(err, check.IsNil)
995 c.Check(pos, check.Equals, int64(6))
996 f.Seek(0, io.SeekStart)
998 c.Check(buf[:n], check.DeepEquals, []byte{1, 2, 3, 4, 5, 6})
999 c.Check(err, check.Equals, io.EOF)
1002 f, err = fs.OpenFile("append", os.O_RDWR|os.O_APPEND, 0)
1003 c.Assert(err, check.IsNil)
1004 pos, err = f.Seek(0, io.SeekCurrent)
1005 c.Check(pos, check.Equals, int64(0))
1006 c.Check(err, check.IsNil)
1008 pos, _ = f.Seek(0, io.SeekCurrent)
1009 c.Check(pos, check.Equals, int64(3))
1010 f.Write([]byte{7, 8, 9})
1011 pos, err = f.Seek(0, io.SeekCurrent)
1012 c.Check(err, check.IsNil)
1013 c.Check(pos, check.Equals, int64(9))
1016 f, err = fs.OpenFile("wronly", os.O_CREATE|os.O_WRONLY, 0)
1017 c.Assert(err, check.IsNil)
1018 n, err = f.Write([]byte{3, 2, 1})
1019 c.Check(n, check.Equals, 3)
1020 c.Check(err, check.IsNil)
1021 pos, _ = f.Seek(0, io.SeekCurrent)
1022 c.Check(pos, check.Equals, int64(3))
1023 pos, _ = f.Seek(0, io.SeekStart)
1024 c.Check(pos, check.Equals, int64(0))
1025 n, err = f.Read(buf)
1026 c.Check(n, check.Equals, 0)
1027 c.Check(err, check.ErrorMatches, `.*O_WRONLY.*`)
1028 f, err = fs.OpenFile("wronly", os.O_RDONLY, 0)
1029 c.Assert(err, check.IsNil)
1031 c.Check(buf[:n], check.DeepEquals, []byte{3, 2, 1})
1033 f, err = fs.OpenFile("unsupported", os.O_CREATE|os.O_SYNC, 0)
1034 c.Check(f, check.IsNil)
1035 c.Check(err, check.NotNil)
1037 f, err = fs.OpenFile("append", os.O_RDWR|os.O_WRONLY, 0)
1038 c.Check(f, check.IsNil)
1039 c.Check(err, check.ErrorMatches, `invalid flag.*`)
1042 func (s *CollectionFSSuite) TestFlushFullBlocks(c *check.C) {
1043 defer func(wab, mbs int) {
1044 writeAheadBlocks = wab
1046 }(writeAheadBlocks, maxBlockSize)
1047 writeAheadBlocks = 2
1050 proceed := make(chan struct{})
1051 var started, concurrent int32
1053 s.kc.onPut = func([]byte) {
1054 atomic.AddInt32(&concurrent, 1)
1055 switch atomic.AddInt32(&started, 1) {
1057 // Wait until block 2 starts and finishes, and block 3 starts
1060 c.Check(blk2done, check.Equals, true)
1061 case <-time.After(time.Second):
1062 c.Error("timed out")
1065 time.Sleep(time.Millisecond)
1070 time.Sleep(time.Millisecond)
1072 c.Check(atomic.AddInt32(&concurrent, -1) < int32(writeAheadBlocks), check.Equals, true)
1075 fs, err := (&Collection{}).FileSystem(s.client, s.kc)
1076 c.Assert(err, check.IsNil)
1077 f, err := fs.OpenFile("50K", os.O_WRONLY|os.O_CREATE, 0)
1078 c.Assert(err, check.IsNil)
1081 data := make([]byte, 500)
1084 for i := 0; i < 100; i++ {
1085 n, err := f.Write(data)
1086 c.Assert(n, check.Equals, len(data))
1087 c.Assert(err, check.IsNil)
1090 currentMemExtents := func() (memExtents []int) {
1091 for idx, e := range f.(*filehandle).inode.(*filenode).segments {
1094 memExtents = append(memExtents, idx)
1099 f.(*filehandle).inode.(*filenode).waitPrune()
1100 c.Check(currentMemExtents(), check.HasLen, 1)
1102 m, err := fs.MarshalManifest(".")
1103 c.Check(m, check.Matches, `[^:]* 0:50000:50K\n`)
1104 c.Check(err, check.IsNil)
1105 c.Check(currentMemExtents(), check.HasLen, 0)
1108 func (s *CollectionFSSuite) TestBrokenManifests(c *check.C) {
1109 for _, txt := range []string{
1113 ". d41d8cd98f00b204e9800998ecf8427e+0\n",
1114 ". d41d8cd98f00b204e9800998ecf8427e+0 \n",
1117 ". 0:0:foo 0:0:bar\n",
1118 ". d41d8cd98f00b204e9800998ecf8427e 0:0:foo\n",
1119 ". d41d8cd98f00b204e9800998ecf8427e+0 :0:0:foo\n",
1120 ". d41d8cd98f00b204e9800998ecf8427e+0 foo:0:foo\n",
1121 ". d41d8cd98f00b204e9800998ecf8427e+0 0:foo:foo\n",
1122 ". d41d8cd98f00b204e9800998ecf8427e+1 0:1:foo 1:1:bar\n",
1123 ". d41d8cd98f00b204e9800998ecf8427e+1 0:1:\\056\n",
1124 ". d41d8cd98f00b204e9800998ecf8427e+1 0:1:\\056\\057\\056\n",
1125 ". d41d8cd98f00b204e9800998ecf8427e+1 0:1:.\n",
1126 ". d41d8cd98f00b204e9800998ecf8427e+1 0:1:..\n",
1127 ". d41d8cd98f00b204e9800998ecf8427e+0 0:0:..\n",
1128 ". d41d8cd98f00b204e9800998ecf8427e+0 0:0:foo/..\n",
1129 ". d41d8cd98f00b204e9800998ecf8427e+1 0:0:foo\n./foo d41d8cd98f00b204e9800998ecf8427e+1 0:0:bar\n",
1130 "./foo d41d8cd98f00b204e9800998ecf8427e+1 0:0:bar\n. d41d8cd98f00b204e9800998ecf8427e+1 0:0:foo\n",
1133 fs, err := (&Collection{ManifestText: txt}).FileSystem(s.client, s.kc)
1134 c.Check(fs, check.IsNil)
1135 c.Logf("-> %s", err)
1136 c.Check(err, check.NotNil)
1140 func (s *CollectionFSSuite) TestEdgeCaseManifests(c *check.C) {
1141 for _, txt := range []string{
1143 ". d41d8cd98f00b204e9800998ecf8427e+0 0:0:foo\n",
1144 ". d41d8cd98f00b204e9800998ecf8427e+0 0:0:...\n",
1145 ". d41d8cd98f00b204e9800998ecf8427e+0 0:0:. 0:0:. 0:0:\\056 0:0:\\056\n",
1146 ". d41d8cd98f00b204e9800998ecf8427e+0 0:0:foo/. 0:0:. 0:0:foo\\057bar\\057\\056\n",
1147 ". d41d8cd98f00b204e9800998ecf8427e+0 0:0:foo 0:0:foo 0:0:bar\n",
1148 ". d41d8cd98f00b204e9800998ecf8427e+0 0:0:foo/bar\n./foo d41d8cd98f00b204e9800998ecf8427e+0 0:0:bar\n",
1151 fs, err := (&Collection{ManifestText: txt}).FileSystem(s.client, s.kc)
1152 c.Check(err, check.IsNil)
1153 c.Check(fs, check.NotNil)
1157 func (s *CollectionFSSuite) checkMemSize(c *check.C, f File) {
1158 fn := f.(*filehandle).inode.(*filenode)
1160 for _, seg := range fn.segments {
1161 if e, ok := seg.(*memSegment); ok {
1162 memsize += int64(len(e.buf))
1165 c.Check(fn.memsize, check.Equals, memsize)
1168 type CollectionFSUnitSuite struct{}
1170 var _ = check.Suite(&CollectionFSUnitSuite{})
1172 // expect ~2 seconds to load a manifest with 256K files
1173 func (s *CollectionFSUnitSuite) TestLargeManifest(c *check.C) {
1174 if testing.Short() {
1183 mb := bytes.NewBuffer(make([]byte, 0, 40000000))
1184 for i := 0; i < dirCount; i++ {
1185 fmt.Fprintf(mb, "./dir%d", i)
1186 for j := 0; j <= fileCount; j++ {
1187 fmt.Fprintf(mb, " %032x+42+A%040x@%08x", j, j, j)
1189 for j := 0; j < fileCount; j++ {
1190 fmt.Fprintf(mb, " %d:%d:dir%d/file%d", j*42+21, 42, j, j)
1192 mb.Write([]byte{'\n'})
1194 coll := Collection{ManifestText: mb.String()}
1195 c.Logf("%s built", time.Now())
1197 var memstats runtime.MemStats
1198 runtime.ReadMemStats(&memstats)
1199 c.Logf("%s Alloc=%d Sys=%d", time.Now(), memstats.Alloc, memstats.Sys)
1201 f, err := coll.FileSystem(nil, nil)
1202 c.Check(err, check.IsNil)
1203 c.Logf("%s loaded", time.Now())
1204 c.Check(f.Size(), check.Equals, int64(42*dirCount*fileCount))
1206 for i := 0; i < dirCount; i++ {
1207 for j := 0; j < fileCount; j++ {
1208 f.Stat(fmt.Sprintf("./dir%d/dir%d/file%d", i, j, j))
1211 c.Logf("%s Stat() x %d", time.Now(), dirCount*fileCount)
1213 runtime.ReadMemStats(&memstats)
1214 c.Logf("%s Alloc=%d Sys=%d", time.Now(), memstats.Alloc, memstats.Sys)
1217 // Gocheck boilerplate
1218 func Test(t *testing.T) {