1 // Copyright (C) The Arvados Authors. All rights reserved.
3 // SPDX-License-Identifier: Apache-2.0
26 "git.curoverse.com/arvados.git/sdk/go/arvadostest"
27 check "gopkg.in/check.v1"
30 var _ = check.Suite(&CollectionFSSuite{})
32 type keepClientStub struct {
33 blocks map[string][]byte
34 refreshable map[string]bool
35 onPut func(bufcopy []byte) // called from PutB, before acquiring lock
39 var errStub404 = errors.New("404 block not found")
41 func (kcs *keepClientStub) ReadAt(locator string, p []byte, off int) (int, error) {
44 buf := kcs.blocks[locator[:32]]
48 return copy(p, buf[off:]), nil
51 func (kcs *keepClientStub) PutB(p []byte) (string, int, error) {
52 locator := fmt.Sprintf("%x+%d+A12345@abcde", md5.Sum(p), len(p))
53 buf := make([]byte, len(p))
60 kcs.blocks[locator[:32]] = buf
61 return locator, 1, nil
64 var localOrRemoteSignature = regexp.MustCompile(`\+[AR][^+]*`)
66 func (kcs *keepClientStub) LocalLocator(locator string) (string, error) {
69 if strings.Contains(locator, "+R") {
70 if len(locator) < 32 {
71 return "", fmt.Errorf("bad locator: %q", locator)
73 if _, ok := kcs.blocks[locator[:32]]; !ok && !kcs.refreshable[locator[:32]] {
74 return "", fmt.Errorf("kcs.refreshable[%q]==false", locator)
77 fakeSig := fmt.Sprintf("+A%x@%x", sha1.Sum(nil), time.Now().Add(time.Hour*24*14).Unix())
78 return localOrRemoteSignature.ReplaceAllLiteralString(locator, fakeSig), nil
81 type CollectionFSSuite struct {
84 fs CollectionFileSystem
88 func (s *CollectionFSSuite) SetUpTest(c *check.C) {
89 s.client = NewClientFromEnv()
90 err := s.client.RequestAndDecode(&s.coll, "GET", "arvados/v1/collections/"+arvadostest.FooAndBarFilesInDirUUID, nil, nil)
91 c.Assert(err, check.IsNil)
92 s.kc = &keepClientStub{
93 blocks: map[string][]byte{
94 "3858f62230ac3c915f300c664312c63f": []byte("foobar"),
96 s.fs, err = s.coll.FileSystem(s.client, s.kc)
97 c.Assert(err, check.IsNil)
100 func (s *CollectionFSSuite) TestHttpFileSystemInterface(c *check.C) {
101 _, ok := s.fs.(http.FileSystem)
102 c.Check(ok, check.Equals, true)
105 func (s *CollectionFSSuite) TestColonInFilename(c *check.C) {
106 fs, err := (&Collection{
107 ManifestText: "./foo:foo 3858f62230ac3c915f300c664312c63f+3 0:3:bar:bar\n",
108 }).FileSystem(s.client, s.kc)
109 c.Assert(err, check.IsNil)
111 f, err := fs.Open("/foo:foo")
112 c.Assert(err, check.IsNil)
114 fis, err := f.Readdir(0)
115 c.Check(err, check.IsNil)
116 c.Check(len(fis), check.Equals, 1)
117 c.Check(fis[0].Name(), check.Equals, "bar:bar")
120 func (s *CollectionFSSuite) TestReaddirFull(c *check.C) {
121 f, err := s.fs.Open("/dir1")
122 c.Assert(err, check.IsNil)
125 c.Assert(err, check.IsNil)
126 c.Check(st.Size(), check.Equals, int64(2))
127 c.Check(st.IsDir(), check.Equals, true)
129 fis, err := f.Readdir(0)
130 c.Check(err, check.IsNil)
131 c.Check(len(fis), check.Equals, 2)
133 c.Check(fis[0].Size(), check.Equals, int64(3))
137 func (s *CollectionFSSuite) TestReaddirLimited(c *check.C) {
138 f, err := s.fs.Open("./dir1")
139 c.Assert(err, check.IsNil)
141 fis, err := f.Readdir(1)
142 c.Check(err, check.IsNil)
143 c.Check(len(fis), check.Equals, 1)
145 c.Check(fis[0].Size(), check.Equals, int64(3))
148 fis, err = f.Readdir(1)
149 c.Check(err, check.IsNil)
150 c.Check(len(fis), check.Equals, 1)
152 c.Check(fis[0].Size(), check.Equals, int64(3))
155 fis, err = f.Readdir(1)
156 c.Check(len(fis), check.Equals, 0)
157 c.Check(err, check.NotNil)
158 c.Check(err, check.Equals, io.EOF)
160 f, err = s.fs.Open("dir1")
161 c.Assert(err, check.IsNil)
162 fis, err = f.Readdir(1)
163 c.Check(len(fis), check.Equals, 1)
164 c.Assert(err, check.IsNil)
165 fis, err = f.Readdir(2)
166 c.Check(len(fis), check.Equals, 1)
167 c.Assert(err, check.IsNil)
168 fis, err = f.Readdir(2)
169 c.Check(len(fis), check.Equals, 0)
170 c.Assert(err, check.Equals, io.EOF)
173 func (s *CollectionFSSuite) TestPathMunge(c *check.C) {
174 for _, path := range []string{".", "/", "./", "///", "/../", "/./.."} {
175 f, err := s.fs.Open(path)
176 c.Assert(err, check.IsNil)
179 c.Assert(err, check.IsNil)
180 c.Check(st.Size(), check.Equals, int64(1))
181 c.Check(st.IsDir(), check.Equals, true)
183 for _, path := range []string{"/dir1", "dir1", "./dir1", "///dir1//.//", "../dir1/../dir1/"} {
185 f, err := s.fs.Open(path)
186 c.Assert(err, check.IsNil)
189 c.Assert(err, check.IsNil)
190 c.Check(st.Size(), check.Equals, int64(2))
191 c.Check(st.IsDir(), check.Equals, true)
195 func (s *CollectionFSSuite) TestNotExist(c *check.C) {
196 for _, path := range []string{"/no", "no", "./no", "n/o", "/n/o"} {
197 f, err := s.fs.Open(path)
198 c.Assert(f, check.IsNil)
199 c.Assert(err, check.NotNil)
200 c.Assert(os.IsNotExist(err), check.Equals, true)
204 func (s *CollectionFSSuite) TestReadOnlyFile(c *check.C) {
205 f, err := s.fs.OpenFile("/dir1/foo", os.O_RDONLY, 0)
206 c.Assert(err, check.IsNil)
208 c.Assert(err, check.IsNil)
209 c.Check(st.Size(), check.Equals, int64(3))
210 n, err := f.Write([]byte("bar"))
211 c.Check(n, check.Equals, 0)
212 c.Check(err, check.Equals, ErrReadOnlyFile)
215 func (s *CollectionFSSuite) TestCreateFile(c *check.C) {
216 f, err := s.fs.OpenFile("/new-file 1", os.O_RDWR|os.O_CREATE, 0)
217 c.Assert(err, check.IsNil)
219 c.Assert(err, check.IsNil)
220 c.Check(st.Size(), check.Equals, int64(0))
222 n, err := f.Write([]byte("bar"))
223 c.Check(n, check.Equals, 3)
224 c.Check(err, check.IsNil)
226 c.Check(f.Close(), check.IsNil)
228 f, err = s.fs.OpenFile("/new-file 1", os.O_RDWR|os.O_CREATE|os.O_EXCL, 0)
229 c.Check(f, check.IsNil)
230 c.Assert(err, check.NotNil)
232 f, err = s.fs.OpenFile("/new-file 1", os.O_RDWR, 0)
233 c.Assert(err, check.IsNil)
235 c.Assert(err, check.IsNil)
236 c.Check(st.Size(), check.Equals, int64(3))
238 c.Check(f.Close(), check.IsNil)
240 m, err := s.fs.MarshalManifest(".")
241 c.Assert(err, check.IsNil)
242 c.Check(m, check.Matches, `. 37b51d194a7513e45b56f6524f2d51f2\+3\+\S+ 0:3:new-file\\0401\n./dir1 .* 3:3:bar 0:3:foo\n`)
245 func (s *CollectionFSSuite) TestReadWriteFile(c *check.C) {
247 defer func() { maxBlockSize = 2 << 26 }()
249 f, err := s.fs.OpenFile("/dir1/foo", os.O_RDWR, 0)
250 c.Assert(err, check.IsNil)
253 c.Assert(err, check.IsNil)
254 c.Check(st.Size(), check.Equals, int64(3))
256 f2, err := s.fs.OpenFile("/dir1/foo", os.O_RDWR, 0)
257 c.Assert(err, check.IsNil)
260 buf := make([]byte, 64)
261 n, err := f.Read(buf)
262 c.Check(n, check.Equals, 3)
263 c.Check(err, check.Equals, io.EOF)
264 c.Check(string(buf[:3]), check.DeepEquals, "foo")
266 pos, err := f.Seek(-2, io.SeekCurrent)
267 c.Check(pos, check.Equals, int64(1))
268 c.Check(err, check.IsNil)
270 // Split a storedExtent in two, and insert a memExtent
271 n, err = f.Write([]byte("*"))
272 c.Check(n, check.Equals, 1)
273 c.Check(err, check.IsNil)
275 pos, err = f.Seek(0, io.SeekCurrent)
276 c.Check(pos, check.Equals, int64(2))
277 c.Check(err, check.IsNil)
279 pos, err = f.Seek(0, io.SeekStart)
280 c.Check(pos, check.Equals, int64(0))
281 c.Check(err, check.IsNil)
283 rbuf, err := ioutil.ReadAll(f)
284 c.Check(len(rbuf), check.Equals, 3)
285 c.Check(err, check.IsNil)
286 c.Check(string(rbuf), check.Equals, "f*o")
288 // Write multiple blocks in one call
289 f.Seek(1, io.SeekStart)
290 n, err = f.Write([]byte("0123456789abcdefg"))
291 c.Check(n, check.Equals, 17)
292 c.Check(err, check.IsNil)
293 pos, err = f.Seek(0, io.SeekCurrent)
294 c.Check(pos, check.Equals, int64(18))
295 c.Check(err, check.IsNil)
296 pos, err = f.Seek(-18, io.SeekCurrent)
297 c.Check(pos, check.Equals, int64(0))
298 c.Check(err, check.IsNil)
299 n, err = io.ReadFull(f, buf)
300 c.Check(n, check.Equals, 18)
301 c.Check(err, check.Equals, io.ErrUnexpectedEOF)
302 c.Check(string(buf[:n]), check.Equals, "f0123456789abcdefg")
304 buf2, err := ioutil.ReadAll(f2)
305 c.Check(err, check.IsNil)
306 c.Check(string(buf2), check.Equals, "f0123456789abcdefg")
308 // truncate to current size
310 c.Check(err, check.IsNil)
311 f2.Seek(0, io.SeekStart)
312 buf2, err = ioutil.ReadAll(f2)
313 c.Check(err, check.IsNil)
314 c.Check(string(buf2), check.Equals, "f0123456789abcdefg")
316 // shrink to zero some data
318 f2.Seek(0, io.SeekStart)
319 buf2, err = ioutil.ReadAll(f2)
320 c.Check(err, check.IsNil)
321 c.Check(string(buf2), check.Equals, "f0123456789abcd")
323 // grow to partial block/extent
325 f2.Seek(0, io.SeekStart)
326 buf2, err = ioutil.ReadAll(f2)
327 c.Check(err, check.IsNil)
328 c.Check(string(buf2), check.Equals, "f0123456789abcd\x00\x00\x00\x00\x00")
331 f2.Seek(0, io.SeekStart)
332 f2.Write([]byte("12345678abcdefghijkl"))
334 // grow to block/extent boundary
336 f2.Seek(0, io.SeekStart)
337 buf2, err = ioutil.ReadAll(f2)
338 c.Check(err, check.IsNil)
339 c.Check(len(buf2), check.Equals, 64)
340 c.Check(len(f.(*filehandle).inode.(*filenode).segments), check.Equals, 8)
342 // shrink to block/extent boundary
344 c.Check(err, check.IsNil)
345 f2.Seek(0, io.SeekStart)
346 buf2, err = ioutil.ReadAll(f2)
347 c.Check(err, check.IsNil)
348 c.Check(len(buf2), check.Equals, 32)
349 c.Check(len(f.(*filehandle).inode.(*filenode).segments), check.Equals, 4)
351 // shrink to partial block/extent
353 c.Check(err, check.IsNil)
354 f2.Seek(0, io.SeekStart)
355 buf2, err = ioutil.ReadAll(f2)
356 c.Check(err, check.IsNil)
357 c.Check(string(buf2), check.Equals, "12345678abcdefg")
358 c.Check(len(f.(*filehandle).inode.(*filenode).segments), check.Equals, 2)
360 // Force flush to ensure the block "12345678" gets stored, so
361 // we know what to expect in the final manifest below.
362 _, err = s.fs.MarshalManifest(".")
363 c.Check(err, check.IsNil)
365 // Truncate to size=3 while f2's ptr is at 15
367 c.Check(err, check.IsNil)
368 buf2, err = ioutil.ReadAll(f2)
369 c.Check(err, check.IsNil)
370 c.Check(string(buf2), check.Equals, "")
371 f2.Seek(0, io.SeekStart)
372 buf2, err = ioutil.ReadAll(f2)
373 c.Check(err, check.IsNil)
374 c.Check(string(buf2), check.Equals, "123")
375 c.Check(len(f.(*filehandle).inode.(*filenode).segments), check.Equals, 1)
377 m, err := s.fs.MarshalManifest(".")
378 c.Check(err, check.IsNil)
379 m = regexp.MustCompile(`\+A[^\+ ]+`).ReplaceAllLiteralString(m, "")
380 c.Check(m, check.Equals, "./dir1 3858f62230ac3c915f300c664312c63f+6 25d55ad283aa400af464c76d713c07ad+8 3:3:bar 6:3:foo\n")
381 c.Check(s.fs.Size(), check.Equals, int64(6))
384 func (s *CollectionFSSuite) TestSeekSparse(c *check.C) {
385 fs, err := (&Collection{}).FileSystem(s.client, s.kc)
386 c.Assert(err, check.IsNil)
387 f, err := fs.OpenFile("test", os.O_CREATE|os.O_RDWR, 0755)
388 c.Assert(err, check.IsNil)
391 checkSize := func(size int64) {
393 c.Assert(err, check.IsNil)
394 c.Check(fi.Size(), check.Equals, size)
396 f, err := fs.OpenFile("test", os.O_CREATE|os.O_RDWR, 0755)
397 c.Assert(err, check.IsNil)
400 c.Check(err, check.IsNil)
401 c.Check(fi.Size(), check.Equals, size)
402 pos, err := f.Seek(0, io.SeekEnd)
403 c.Check(err, check.IsNil)
404 c.Check(pos, check.Equals, size)
407 f.Seek(2, io.SeekEnd)
412 f.Seek(2, io.SeekCurrent)
417 f.Seek(8, io.SeekStart)
419 n, err := f.Read(make([]byte, 1))
420 c.Check(n, check.Equals, 0)
421 c.Check(err, check.Equals, io.EOF)
423 f.Write([]byte{1, 2, 3})
427 func (s *CollectionFSSuite) TestMarshalCopiesRemoteBlocks(c *check.C) {
430 hash := map[string]string{
431 foo: fmt.Sprintf("%x", md5.Sum([]byte(foo))),
432 bar: fmt.Sprintf("%x", md5.Sum([]byte(bar))),
435 fs, err := (&Collection{
436 ManifestText: ". " + hash[foo] + "+3+Rzaaaa-foo@bab " + hash[bar] + "+3+A12345@ffffff 0:2:fo.txt 2:4:obar.txt\n",
437 }).FileSystem(s.client, s.kc)
438 c.Assert(err, check.IsNil)
439 manifest, err := fs.MarshalManifest(".")
440 c.Check(manifest, check.Equals, "")
441 c.Check(err, check.NotNil)
443 s.kc.refreshable = map[string]bool{hash[bar]: true}
445 for _, sigIn := range []string{"Rzaaaa-foo@bab", "A12345@abcde"} {
446 fs, err = (&Collection{
447 ManifestText: ". " + hash[foo] + "+3+A12345@fffff " + hash[bar] + "+3+" + sigIn + " 0:2:fo.txt 2:4:obar.txt\n",
448 }).FileSystem(s.client, s.kc)
449 c.Assert(err, check.IsNil)
450 manifest, err := fs.MarshalManifest(".")
451 c.Check(err, check.IsNil)
452 // Both blocks should now have +A signatures.
453 c.Check(manifest, check.Matches, `.*\+A.* .*\+A.*\n`)
454 c.Check(manifest, check.Not(check.Matches), `.*\+R.*\n`)
458 func (s *CollectionFSSuite) TestMarshalSmallBlocks(c *check.C) {
460 defer func() { maxBlockSize = 2 << 26 }()
463 s.fs, err = (&Collection{}).FileSystem(s.client, s.kc)
464 c.Assert(err, check.IsNil)
465 for _, name := range []string{"foo", "bar", "baz"} {
466 f, err := s.fs.OpenFile(name, os.O_WRONLY|os.O_CREATE, 0)
467 c.Assert(err, check.IsNil)
468 f.Write([]byte(name))
472 m, err := s.fs.MarshalManifest(".")
473 c.Check(err, check.IsNil)
474 m = regexp.MustCompile(`\+A[^\+ ]+`).ReplaceAllLiteralString(m, "")
475 c.Check(m, check.Equals, ". c3c23db5285662ef7172373df0003206+6 acbd18db4cc2f85cedef654fccc4a4d8+3 0:3:bar 3:3:baz 6:3:foo\n")
478 func (s *CollectionFSSuite) TestMkdir(c *check.C) {
479 err := s.fs.Mkdir("foo/bar", 0755)
480 c.Check(err, check.Equals, os.ErrNotExist)
482 f, err := s.fs.OpenFile("foo/bar", os.O_CREATE, 0)
483 c.Check(err, check.Equals, os.ErrNotExist)
485 err = s.fs.Mkdir("foo", 0755)
486 c.Check(err, check.IsNil)
488 f, err = s.fs.OpenFile("foo/bar", os.O_CREATE|os.O_WRONLY, 0)
489 c.Check(err, check.IsNil)
492 f.Write([]byte("foo"))
495 // mkdir fails if a file already exists with that name
496 err = s.fs.Mkdir("foo/bar", 0755)
497 c.Check(err, check.NotNil)
499 err = s.fs.Remove("foo/bar")
500 c.Check(err, check.IsNil)
502 // mkdir succeeds after the file is deleted
503 err = s.fs.Mkdir("foo/bar", 0755)
504 c.Check(err, check.IsNil)
506 // creating a file in a nonexistent subdir should still fail
507 f, err = s.fs.OpenFile("foo/bar/baz/foo.txt", os.O_CREATE|os.O_WRONLY, 0)
508 c.Check(err, check.Equals, os.ErrNotExist)
510 f, err = s.fs.OpenFile("foo/bar/foo.txt", os.O_CREATE|os.O_WRONLY, 0)
511 c.Check(err, check.IsNil)
514 f.Write([]byte("foo"))
517 // creating foo/bar as a regular file should fail
518 f, err = s.fs.OpenFile("foo/bar", os.O_CREATE|os.O_EXCL, 0)
519 c.Check(err, check.NotNil)
521 // creating foo/bar as a directory should fail
522 f, err = s.fs.OpenFile("foo/bar", os.O_CREATE|os.O_EXCL, os.ModeDir)
523 c.Check(err, check.NotNil)
524 err = s.fs.Mkdir("foo/bar", 0755)
525 c.Check(err, check.NotNil)
527 m, err := s.fs.MarshalManifest(".")
528 c.Check(err, check.IsNil)
529 m = regexp.MustCompile(`\+A[^\+ ]+`).ReplaceAllLiteralString(m, "")
530 c.Check(m, check.Equals, "./dir1 3858f62230ac3c915f300c664312c63f+6 3:3:bar 0:3:foo\n./foo/bar acbd18db4cc2f85cedef654fccc4a4d8+3 0:3:foo.txt\n")
533 func (s *CollectionFSSuite) TestConcurrentWriters(c *check.C) {
539 defer func() { maxBlockSize = 2 << 26 }()
541 var wg sync.WaitGroup
542 for n := 0; n < 128; n++ {
546 f, err := s.fs.OpenFile("/dir1/foo", os.O_RDWR, 0)
547 c.Assert(err, check.IsNil)
549 for i := 0; i < 1024; i++ {
553 _, err := s.fs.MarshalManifest(".")
554 c.Check(err, check.IsNil)
556 f.Truncate(int64(rand.Intn(64)))
558 f.Seek(int64(rand.Intn(64)), io.SeekStart)
560 _, err := f.Write([]byte("beep boop"))
561 c.Check(err, check.IsNil)
563 _, err := ioutil.ReadAll(f)
564 c.Check(err, check.IsNil)
571 f, err := s.fs.OpenFile("/dir1/foo", os.O_RDWR, 0)
572 c.Assert(err, check.IsNil)
574 buf, err := ioutil.ReadAll(f)
575 c.Check(err, check.IsNil)
576 c.Logf("after lots of random r/w/seek/trunc, buf is %q", buf)
579 func (s *CollectionFSSuite) TestRandomWrites(c *check.C) {
581 defer func() { maxBlockSize = 2 << 26 }()
584 s.fs, err = (&Collection{}).FileSystem(s.client, s.kc)
585 c.Assert(err, check.IsNil)
588 const ngoroutines = 256
590 var wg sync.WaitGroup
591 for n := 0; n < ngoroutines; n++ {
595 expect := make([]byte, 0, 64)
596 wbytes := []byte("there's no simple explanation for anything important that any of us do")
597 f, err := s.fs.OpenFile(fmt.Sprintf("random-%d", n), os.O_RDWR|os.O_CREATE|os.O_EXCL, 0)
598 c.Assert(err, check.IsNil)
600 for i := 0; i < nfiles; i++ {
601 trunc := rand.Intn(65)
602 woff := rand.Intn(trunc + 1)
603 wbytes = wbytes[:rand.Intn(64-woff+1)]
604 for buf, i := expect[:cap(expect)], len(expect); i < trunc; i++ {
607 expect = expect[:trunc]
608 if trunc < woff+len(wbytes) {
609 expect = expect[:woff+len(wbytes)]
611 copy(expect[woff:], wbytes)
612 f.Truncate(int64(trunc))
613 pos, err := f.Seek(int64(woff), io.SeekStart)
614 c.Check(pos, check.Equals, int64(woff))
615 c.Check(err, check.IsNil)
616 n, err := f.Write(wbytes)
617 c.Check(n, check.Equals, len(wbytes))
618 c.Check(err, check.IsNil)
619 pos, err = f.Seek(0, io.SeekStart)
620 c.Check(pos, check.Equals, int64(0))
621 c.Check(err, check.IsNil)
622 buf, err := ioutil.ReadAll(f)
623 c.Check(string(buf), check.Equals, string(expect))
624 c.Check(err, check.IsNil)
630 for n := 0; n < ngoroutines; n++ {
631 f, err := s.fs.OpenFile(fmt.Sprintf("random-%d", n), os.O_RDONLY, 0)
632 c.Assert(err, check.IsNil)
633 f.(*filehandle).inode.(*filenode).waitPrune()
638 root, err := s.fs.Open("/")
639 c.Assert(err, check.IsNil)
641 fi, err := root.Readdir(-1)
642 c.Check(err, check.IsNil)
643 c.Check(len(fi), check.Equals, nfiles)
645 _, err = s.fs.MarshalManifest(".")
646 c.Check(err, check.IsNil)
647 // TODO: check manifest content
650 func (s *CollectionFSSuite) TestRemove(c *check.C) {
651 fs, err := (&Collection{}).FileSystem(s.client, s.kc)
652 c.Assert(err, check.IsNil)
653 err = fs.Mkdir("dir0", 0755)
654 c.Assert(err, check.IsNil)
655 err = fs.Mkdir("dir1", 0755)
656 c.Assert(err, check.IsNil)
657 err = fs.Mkdir("dir1/dir2", 0755)
658 c.Assert(err, check.IsNil)
659 err = fs.Mkdir("dir1/dir3", 0755)
660 c.Assert(err, check.IsNil)
662 err = fs.Remove("dir0")
663 c.Check(err, check.IsNil)
664 err = fs.Remove("dir0")
665 c.Check(err, check.Equals, os.ErrNotExist)
667 err = fs.Remove("dir1/dir2/.")
668 c.Check(err, check.Equals, ErrInvalidArgument)
669 err = fs.Remove("dir1/dir2/..")
670 c.Check(err, check.Equals, ErrInvalidArgument)
671 err = fs.Remove("dir1")
672 c.Check(err, check.Equals, ErrDirectoryNotEmpty)
673 err = fs.Remove("dir1/dir2/../../../dir1")
674 c.Check(err, check.Equals, ErrDirectoryNotEmpty)
675 err = fs.Remove("dir1/dir3/")
676 c.Check(err, check.IsNil)
677 err = fs.RemoveAll("dir1")
678 c.Check(err, check.IsNil)
679 err = fs.RemoveAll("dir1")
680 c.Check(err, check.IsNil)
683 func (s *CollectionFSSuite) TestRenameError(c *check.C) {
684 fs, err := (&Collection{}).FileSystem(s.client, s.kc)
685 c.Assert(err, check.IsNil)
686 err = fs.Mkdir("first", 0755)
687 c.Assert(err, check.IsNil)
688 err = fs.Mkdir("first/second", 0755)
689 c.Assert(err, check.IsNil)
690 f, err := fs.OpenFile("first/second/file", os.O_CREATE|os.O_WRONLY, 0755)
691 c.Assert(err, check.IsNil)
692 f.Write([]byte{1, 2, 3, 4, 5})
694 err = fs.Rename("first", "first/second/third")
695 c.Check(err, check.Equals, ErrInvalidArgument)
696 err = fs.Rename("first", "first/third")
697 c.Check(err, check.Equals, ErrInvalidArgument)
698 err = fs.Rename("first/second", "second")
699 c.Check(err, check.IsNil)
700 f, err = fs.OpenFile("second/file", 0, 0)
701 c.Assert(err, check.IsNil)
702 data, err := ioutil.ReadAll(f)
703 c.Check(err, check.IsNil)
704 c.Check(data, check.DeepEquals, []byte{1, 2, 3, 4, 5})
707 func (s *CollectionFSSuite) TestRenameDirectory(c *check.C) {
708 fs, err := (&Collection{}).FileSystem(s.client, s.kc)
709 c.Assert(err, check.IsNil)
710 err = fs.Mkdir("foo", 0755)
711 c.Assert(err, check.IsNil)
712 err = fs.Mkdir("bar", 0755)
713 c.Assert(err, check.IsNil)
714 err = fs.Rename("bar", "baz")
715 c.Check(err, check.IsNil)
716 err = fs.Rename("foo", "baz")
717 c.Check(err, check.NotNil)
718 err = fs.Rename("foo", "baz/")
719 c.Check(err, check.IsNil)
720 err = fs.Rename("baz/foo", ".")
721 c.Check(err, check.Equals, ErrInvalidArgument)
722 err = fs.Rename("baz/foo/", ".")
723 c.Check(err, check.Equals, ErrInvalidArgument)
726 func (s *CollectionFSSuite) TestRename(c *check.C) {
727 fs, err := (&Collection{}).FileSystem(s.client, s.kc)
728 c.Assert(err, check.IsNil)
733 for i := 0; i < outer; i++ {
734 err = fs.Mkdir(fmt.Sprintf("dir%d", i), 0755)
735 c.Assert(err, check.IsNil)
736 for j := 0; j < inner; j++ {
737 err = fs.Mkdir(fmt.Sprintf("dir%d/dir%d", i, j), 0755)
738 c.Assert(err, check.IsNil)
739 for _, fnm := range []string{
740 fmt.Sprintf("dir%d/file%d", i, j),
741 fmt.Sprintf("dir%d/dir%d/file%d", i, j, j),
743 f, err := fs.OpenFile(fnm, os.O_CREATE|os.O_WRONLY, 0755)
744 c.Assert(err, check.IsNil)
745 _, err = f.Write([]byte("beep"))
746 c.Assert(err, check.IsNil)
751 var wg sync.WaitGroup
752 for i := 0; i < outer; i++ {
753 for j := 0; j < inner; j++ {
757 oldname := fmt.Sprintf("dir%d/dir%d/file%d", i, j, j)
758 newname := fmt.Sprintf("dir%d/newfile%d", i, inner-j-1)
759 _, err := fs.Open(newname)
760 c.Check(err, check.Equals, os.ErrNotExist)
761 err = fs.Rename(oldname, newname)
762 c.Check(err, check.IsNil)
763 f, err := fs.Open(newname)
764 c.Check(err, check.IsNil)
771 // oldname does not exist
773 fmt.Sprintf("dir%d/dir%d/missing", i, j),
774 fmt.Sprintf("dir%d/dir%d/file%d", outer-i-1, j, j))
775 c.Check(err, check.ErrorMatches, `.*does not exist`)
777 // newname parent dir does not exist
779 fmt.Sprintf("dir%d/dir%d", i, j),
780 fmt.Sprintf("dir%d/missing/irrelevant", outer-i-1))
781 c.Check(err, check.ErrorMatches, `.*does not exist`)
783 // oldname parent dir is a file
785 fmt.Sprintf("dir%d/file%d/patherror", i, j),
786 fmt.Sprintf("dir%d/irrelevant", i))
787 c.Check(err, check.ErrorMatches, `.*not a directory`)
789 // newname parent dir is a file
791 fmt.Sprintf("dir%d/dir%d/file%d", i, j, j),
792 fmt.Sprintf("dir%d/file%d/patherror", i, inner-j-1))
793 c.Check(err, check.ErrorMatches, `.*not a directory`)
799 f, err := fs.OpenFile("dir1/newfile3", 0, 0)
800 c.Assert(err, check.IsNil)
801 c.Check(f.Size(), check.Equals, int64(4))
802 buf, err := ioutil.ReadAll(f)
803 c.Check(buf, check.DeepEquals, []byte("beep"))
804 c.Check(err, check.IsNil)
805 _, err = fs.Open("dir1/dir1/file1")
806 c.Check(err, check.Equals, os.ErrNotExist)
809 func (s *CollectionFSSuite) TestPersist(c *check.C) {
811 defer func() { maxBlockSize = 2 << 26 }()
814 s.fs, err = (&Collection{}).FileSystem(s.client, s.kc)
815 c.Assert(err, check.IsNil)
816 err = s.fs.Mkdir("d:r", 0755)
817 c.Assert(err, check.IsNil)
819 expect := map[string][]byte{}
821 var wg sync.WaitGroup
822 for _, name := range []string{"random 1", "random:2", "random\\3", "d:r/random4"} {
823 buf := make([]byte, 500)
827 f, err := s.fs.OpenFile(name, os.O_WRONLY|os.O_CREATE, 0)
828 c.Assert(err, check.IsNil)
829 // Note: we don't close the file until after the test
830 // is done. Writes to unclosed files should persist.
836 for i := 0; i < len(buf); i += 5 {
837 _, err := f.Write(buf[i : i+5])
838 c.Assert(err, check.IsNil)
844 m, err := s.fs.MarshalManifest(".")
845 c.Check(err, check.IsNil)
848 root, err := s.fs.Open("/")
849 c.Assert(err, check.IsNil)
851 fi, err := root.Readdir(-1)
852 c.Check(err, check.IsNil)
853 c.Check(len(fi), check.Equals, 4)
855 persisted, err := (&Collection{ManifestText: m}).FileSystem(s.client, s.kc)
856 c.Assert(err, check.IsNil)
858 root, err = persisted.Open("/")
859 c.Assert(err, check.IsNil)
861 fi, err = root.Readdir(-1)
862 c.Check(err, check.IsNil)
863 c.Check(len(fi), check.Equals, 4)
865 for name, content := range expect {
866 c.Logf("read %q", name)
867 f, err := persisted.Open(name)
868 c.Assert(err, check.IsNil)
870 buf, err := ioutil.ReadAll(f)
871 c.Check(err, check.IsNil)
872 c.Check(buf, check.DeepEquals, content)
876 func (s *CollectionFSSuite) TestPersistEmptyFilesAndDirs(c *check.C) {
878 s.fs, err = (&Collection{}).FileSystem(s.client, s.kc)
879 c.Assert(err, check.IsNil)
880 for _, name := range []string{"dir", "dir/zerodir", "empty", "not empty", "not empty/empty", "zero", "zero/zero"} {
881 err = s.fs.Mkdir(name, 0755)
882 c.Assert(err, check.IsNil)
885 expect := map[string][]byte{
892 "dir/zerodir/zero": nil,
893 "zero/zero/zero": nil,
895 for name, data := range expect {
896 f, err := s.fs.OpenFile(name, os.O_WRONLY|os.O_CREATE, 0)
897 c.Assert(err, check.IsNil)
899 _, err := f.Write(data)
900 c.Assert(err, check.IsNil)
905 m, err := s.fs.MarshalManifest(".")
906 c.Check(err, check.IsNil)
909 persisted, err := (&Collection{ManifestText: m}).FileSystem(s.client, s.kc)
910 c.Assert(err, check.IsNil)
912 for name, data := range expect {
913 _, err = persisted.Open("bogus-" + name)
914 c.Check(err, check.NotNil)
916 f, err := persisted.Open(name)
917 c.Assert(err, check.IsNil)
922 buf, err := ioutil.ReadAll(f)
923 c.Check(err, check.IsNil)
924 c.Check(buf, check.DeepEquals, data)
927 expectDir := map[string]int{
930 "not empty/empty": 0,
932 for name, expectLen := range expectDir {
933 _, err := persisted.Open(name + "/bogus")
934 c.Check(err, check.NotNil)
936 d, err := persisted.Open(name)
938 c.Check(err, check.IsNil)
939 fi, err := d.Readdir(-1)
940 c.Check(err, check.IsNil)
941 c.Check(fi, check.HasLen, expectLen)
945 func (s *CollectionFSSuite) TestOpenFileFlags(c *check.C) {
946 fs, err := (&Collection{}).FileSystem(s.client, s.kc)
947 c.Assert(err, check.IsNil)
949 f, err := fs.OpenFile("missing", os.O_WRONLY, 0)
950 c.Check(f, check.IsNil)
951 c.Check(err, check.ErrorMatches, `file does not exist`)
953 f, err = fs.OpenFile("new", os.O_CREATE|os.O_RDONLY, 0)
954 c.Assert(err, check.IsNil)
956 n, err := f.Write([]byte{1, 2, 3})
957 c.Check(n, check.Equals, 0)
958 c.Check(err, check.ErrorMatches, `read-only file`)
959 n, err = f.Read(make([]byte, 1))
960 c.Check(n, check.Equals, 0)
961 c.Check(err, check.Equals, io.EOF)
962 f, err = fs.OpenFile("new", os.O_RDWR, 0)
963 c.Assert(err, check.IsNil)
965 _, err = f.Write([]byte{4, 5, 6})
966 c.Check(err, check.IsNil)
968 c.Assert(err, check.IsNil)
969 c.Check(fi.Size(), check.Equals, int64(3))
971 f, err = fs.OpenFile("new", os.O_TRUNC|os.O_RDWR, 0)
972 c.Assert(err, check.IsNil)
974 pos, err := f.Seek(0, io.SeekEnd)
975 c.Check(pos, check.Equals, int64(0))
976 c.Check(err, check.IsNil)
978 c.Assert(err, check.IsNil)
979 c.Check(fi.Size(), check.Equals, int64(0))
982 buf := make([]byte, 64)
983 f, err = fs.OpenFile("append", os.O_EXCL|os.O_CREATE|os.O_RDWR|os.O_APPEND, 0)
984 c.Assert(err, check.IsNil)
985 f.Write([]byte{1, 2, 3})
986 f.Seek(0, io.SeekStart)
987 n, _ = f.Read(buf[:1])
988 c.Check(n, check.Equals, 1)
989 c.Check(buf[:1], check.DeepEquals, []byte{1})
990 pos, err = f.Seek(0, io.SeekCurrent)
991 c.Assert(err, check.IsNil)
992 c.Check(pos, check.Equals, int64(1))
993 f.Write([]byte{4, 5, 6})
994 pos, err = f.Seek(0, io.SeekCurrent)
995 c.Assert(err, check.IsNil)
996 c.Check(pos, check.Equals, int64(6))
997 f.Seek(0, io.SeekStart)
999 c.Check(buf[:n], check.DeepEquals, []byte{1, 2, 3, 4, 5, 6})
1000 c.Check(err, check.Equals, io.EOF)
1003 f, err = fs.OpenFile("append", os.O_RDWR|os.O_APPEND, 0)
1004 c.Assert(err, check.IsNil)
1005 pos, err = f.Seek(0, io.SeekCurrent)
1006 c.Check(pos, check.Equals, int64(0))
1007 c.Check(err, check.IsNil)
1009 pos, _ = f.Seek(0, io.SeekCurrent)
1010 c.Check(pos, check.Equals, int64(3))
1011 f.Write([]byte{7, 8, 9})
1012 pos, err = f.Seek(0, io.SeekCurrent)
1013 c.Check(err, check.IsNil)
1014 c.Check(pos, check.Equals, int64(9))
1017 f, err = fs.OpenFile("wronly", os.O_CREATE|os.O_WRONLY, 0)
1018 c.Assert(err, check.IsNil)
1019 n, err = f.Write([]byte{3, 2, 1})
1020 c.Check(n, check.Equals, 3)
1021 c.Check(err, check.IsNil)
1022 pos, _ = f.Seek(0, io.SeekCurrent)
1023 c.Check(pos, check.Equals, int64(3))
1024 pos, _ = f.Seek(0, io.SeekStart)
1025 c.Check(pos, check.Equals, int64(0))
1026 n, err = f.Read(buf)
1027 c.Check(n, check.Equals, 0)
1028 c.Check(err, check.ErrorMatches, `.*O_WRONLY.*`)
1029 f, err = fs.OpenFile("wronly", os.O_RDONLY, 0)
1030 c.Assert(err, check.IsNil)
1032 c.Check(buf[:n], check.DeepEquals, []byte{3, 2, 1})
1034 f, err = fs.OpenFile("unsupported", os.O_CREATE|os.O_SYNC, 0)
1035 c.Check(f, check.IsNil)
1036 c.Check(err, check.NotNil)
1038 f, err = fs.OpenFile("append", os.O_RDWR|os.O_WRONLY, 0)
1039 c.Check(f, check.IsNil)
1040 c.Check(err, check.ErrorMatches, `invalid flag.*`)
1043 func (s *CollectionFSSuite) TestFlushFullBlocks(c *check.C) {
1044 defer func(wab, mbs int) {
1045 writeAheadBlocks = wab
1047 }(writeAheadBlocks, maxBlockSize)
1048 writeAheadBlocks = 2
1051 proceed := make(chan struct{})
1052 var started, concurrent int32
1054 s.kc.onPut = func([]byte) {
1055 atomic.AddInt32(&concurrent, 1)
1056 switch atomic.AddInt32(&started, 1) {
1058 // Wait until block 2 starts and finishes, and block 3 starts
1061 c.Check(blk2done, check.Equals, true)
1062 case <-time.After(time.Second):
1063 c.Error("timed out")
1066 time.Sleep(time.Millisecond)
1071 time.Sleep(time.Millisecond)
1073 c.Check(atomic.AddInt32(&concurrent, -1) < int32(writeAheadBlocks), check.Equals, true)
1076 fs, err := (&Collection{}).FileSystem(s.client, s.kc)
1077 c.Assert(err, check.IsNil)
1078 f, err := fs.OpenFile("50K", os.O_WRONLY|os.O_CREATE, 0)
1079 c.Assert(err, check.IsNil)
1082 data := make([]byte, 500)
1085 for i := 0; i < 100; i++ {
1086 n, err := f.Write(data)
1087 c.Assert(n, check.Equals, len(data))
1088 c.Assert(err, check.IsNil)
1091 currentMemExtents := func() (memExtents []int) {
1092 for idx, e := range f.(*filehandle).inode.(*filenode).segments {
1095 memExtents = append(memExtents, idx)
1100 f.(*filehandle).inode.(*filenode).waitPrune()
1101 c.Check(currentMemExtents(), check.HasLen, 1)
1103 m, err := fs.MarshalManifest(".")
1104 c.Check(m, check.Matches, `[^:]* 0:50000:50K\n`)
1105 c.Check(err, check.IsNil)
1106 c.Check(currentMemExtents(), check.HasLen, 0)
1109 func (s *CollectionFSSuite) TestBrokenManifests(c *check.C) {
1110 for _, txt := range []string{
1114 ". d41d8cd98f00b204e9800998ecf8427e+0\n",
1115 ". d41d8cd98f00b204e9800998ecf8427e+0 \n",
1118 ". 0:0:foo 0:0:bar\n",
1119 ". d41d8cd98f00b204e9800998ecf8427e 0:0:foo\n",
1120 ". d41d8cd98f00b204e9800998ecf8427e+0 :0:0:foo\n",
1121 ". d41d8cd98f00b204e9800998ecf8427e+0 foo:0:foo\n",
1122 ". d41d8cd98f00b204e9800998ecf8427e+0 0:foo:foo\n",
1123 ". d41d8cd98f00b204e9800998ecf8427e+1 0:1:foo 1:1:bar\n",
1124 ". d41d8cd98f00b204e9800998ecf8427e+1 0:1:\\056\n",
1125 ". d41d8cd98f00b204e9800998ecf8427e+1 0:1:\\056\\057\\056\n",
1126 ". d41d8cd98f00b204e9800998ecf8427e+1 0:1:.\n",
1127 ". d41d8cd98f00b204e9800998ecf8427e+1 0:1:..\n",
1128 ". d41d8cd98f00b204e9800998ecf8427e+0 0:0:..\n",
1129 ". d41d8cd98f00b204e9800998ecf8427e+0 0:0:foo/..\n",
1130 ". d41d8cd98f00b204e9800998ecf8427e+1 0:0:foo\n./foo d41d8cd98f00b204e9800998ecf8427e+1 0:0:bar\n",
1131 "./foo d41d8cd98f00b204e9800998ecf8427e+1 0:0:bar\n. d41d8cd98f00b204e9800998ecf8427e+1 0:0:foo\n",
1134 fs, err := (&Collection{ManifestText: txt}).FileSystem(s.client, s.kc)
1135 c.Check(fs, check.IsNil)
1136 c.Logf("-> %s", err)
1137 c.Check(err, check.NotNil)
1141 func (s *CollectionFSSuite) TestEdgeCaseManifests(c *check.C) {
1142 for _, txt := range []string{
1144 ". d41d8cd98f00b204e9800998ecf8427e+0 0:0:foo\n",
1145 ". d41d8cd98f00b204e9800998ecf8427e+0 0:0:...\n",
1146 ". d41d8cd98f00b204e9800998ecf8427e+0 0:0:. 0:0:. 0:0:\\056 0:0:\\056\n",
1147 ". d41d8cd98f00b204e9800998ecf8427e+0 0:0:foo/. 0:0:. 0:0:foo\\057bar\\057\\056\n",
1148 ". d41d8cd98f00b204e9800998ecf8427e+0 0:0:foo 0:0:foo 0:0:bar\n",
1149 ". d41d8cd98f00b204e9800998ecf8427e+0 0:0:foo/bar\n./foo d41d8cd98f00b204e9800998ecf8427e+0 0:0:bar\n",
1152 fs, err := (&Collection{ManifestText: txt}).FileSystem(s.client, s.kc)
1153 c.Check(err, check.IsNil)
1154 c.Check(fs, check.NotNil)
1158 func (s *CollectionFSSuite) checkMemSize(c *check.C, f File) {
1159 fn := f.(*filehandle).inode.(*filenode)
1161 for _, seg := range fn.segments {
1162 if e, ok := seg.(*memSegment); ok {
1163 memsize += int64(len(e.buf))
1166 c.Check(fn.memsize, check.Equals, memsize)
1169 type CollectionFSUnitSuite struct{}
1171 var _ = check.Suite(&CollectionFSUnitSuite{})
1173 // expect ~2 seconds to load a manifest with 256K files
1174 func (s *CollectionFSUnitSuite) TestLargeManifest(c *check.C) {
1175 if testing.Short() {
1184 mb := bytes.NewBuffer(make([]byte, 0, 40000000))
1185 for i := 0; i < dirCount; i++ {
1186 fmt.Fprintf(mb, "./dir%d", i)
1187 for j := 0; j <= fileCount; j++ {
1188 fmt.Fprintf(mb, " %032x+42+A%040x@%08x", j, j, j)
1190 for j := 0; j < fileCount; j++ {
1191 fmt.Fprintf(mb, " %d:%d:dir%d/file%d", j*42+21, 42, j, j)
1193 mb.Write([]byte{'\n'})
1195 coll := Collection{ManifestText: mb.String()}
1196 c.Logf("%s built", time.Now())
1198 var memstats runtime.MemStats
1199 runtime.ReadMemStats(&memstats)
1200 c.Logf("%s Alloc=%d Sys=%d", time.Now(), memstats.Alloc, memstats.Sys)
1202 f, err := coll.FileSystem(nil, nil)
1203 c.Check(err, check.IsNil)
1204 c.Logf("%s loaded", time.Now())
1205 c.Check(f.Size(), check.Equals, int64(42*dirCount*fileCount))
1207 for i := 0; i < dirCount; i++ {
1208 for j := 0; j < fileCount; j++ {
1209 f.Stat(fmt.Sprintf("./dir%d/dir%d/file%d", i, j, j))
1212 c.Logf("%s Stat() x %d", time.Now(), dirCount*fileCount)
1214 runtime.ReadMemStats(&memstats)
1215 c.Logf("%s Alloc=%d Sys=%d", time.Now(), memstats.Alloc, memstats.Sys)
1218 // Gocheck boilerplate
1219 func Test(t *testing.T) {