import logging
import multiprocessing
import run_test_server
+import mock
+import re
-logger = logging.getLogger('arvados.arv-mount')
+from mount_test_base import MountTestBase
-class MountTestBase(unittest.TestCase):
- def setUp(self):
- # The underlying C implementation of open() makes a fstat() syscall
- # with the GIL still held. When the GETATTR message comes back to
- # llfuse (which in these tests is in the same interpreter process) it
- # can't acquire the GIL, so it can't service the fstat() call, so it
- # deadlocks. The workaround is to run some of our test code in a
- # separate process. Forturnately the multiprocessing module makes this
- # relatively easy.
- self.pool = multiprocessing.Pool(1)
-
- self.keeptmp = tempfile.mkdtemp()
- os.environ['KEEP_LOCAL_STORE'] = self.keeptmp
- self.mounttmp = tempfile.mkdtemp()
- run_test_server.run()
- run_test_server.authorize_with("admin")
- self.api = arvados.safeapi.ThreadSafeApiCache(arvados.config.settings())
-
- def make_mount(self, root_class, **root_kwargs):
- self.operations = fuse.Operations(os.getuid(), os.getgid())
- self.operations.inodes.add_entry(root_class(
- llfuse.ROOT_INODE, self.operations.inodes, self.api, 0, **root_kwargs))
- llfuse.init(self.operations, self.mounttmp, [])
- threading.Thread(None, llfuse.main).start()
- # wait until the driver is finished initializing
- self.operations.initlock.wait()
- return self.operations.inodes[llfuse.ROOT_INODE]
-
- def tearDown(self):
- self.pool.close()
- self.pool = None
-
- # llfuse.close is buggy, so use fusermount instead.
- #llfuse.close(unmount=True)
- count = 0
- success = 1
- while (count < 9 and success != 0):
- success = subprocess.call(["fusermount", "-u", self.mounttmp])
- time.sleep(0.5)
- count += 1
-
- os.rmdir(self.mounttmp)
- shutil.rmtree(self.keeptmp)
- run_test_server.reset()
-
- def assertDirContents(self, subdir, expect_content):
- path = self.mounttmp
- if subdir:
- path = os.path.join(path, subdir)
- self.assertEqual(sorted(expect_content), sorted(llfuse.listdir(path)))
+logger = logging.getLogger('arvados.arv-mount')
class FuseMountTest(MountTestBase):
cw.write("data 1")
cw.start_new_file('thing2.txt')
cw.write("data 2")
- cw.start_new_stream('dir1')
+ cw.start_new_stream('dir1')
cw.start_new_file('thing3.txt')
cw.write("data 3")
cw.start_new_file('thing4.txt')
cw.write("data 8")
cw.start_new_stream('edgecases')
- for f in ":/./../.../-/*/\x01\\/ ".split("/"):
+ for f in ":/.../-/*/\x01\\/ ".split("/"):
cw.start_new_file(f)
cw.write('x')
- for f in ":/../.../-/*/\x01\\/ ".split("/"):
+ for f in ":/.../-/*/\x01\\/ ".split("/"):
cw.start_new_stream('edgecases/dirs/' + f)
cw.start_new_file('x/x')
cw.write('x')
self.assertDirContents('dir2', ['thing5.txt', 'thing6.txt', 'dir3'])
self.assertDirContents('dir2/dir3', ['thing7.txt', 'thing8.txt'])
self.assertDirContents('edgecases',
- "dirs/:/_/__/.../-/*/\x01\\/ ".split("/"))
+ "dirs/:/.../-/*/\x01\\/ ".split("/"))
self.assertDirContents('edgecases/dirs',
- ":/__/.../-/*/\x01\\/ ".split("/"))
+ ":/.../-/*/\x01\\/ ".split("/"))
files = {'thing1.txt': 'data 1',
'thing2.txt': 'data 2',
class FuseMagicTest(MountTestBase):
- def setUp(self):
- super(FuseMagicTest, self).setUp()
+ def setUp(self, api=None):
+ super(FuseMagicTest, self).setUp(api=api)
cw = arvados.CollectionWriter()
cw.write("data 1")
self.testcollection = cw.finish()
- self.api.collections().create(body={"manifest_text":cw.manifest_text()}).execute()
+ self.test_manifest = cw.manifest_text()
+ self.api.collections().create(body={"manifest_text":self.test_manifest}).execute()
def runTest(self):
self.make_mount(fuse.MagicDirectory)
self.assertEqual(["GNU_General_Public_License,_version_3.pdf"], d3)
-def fuseModifyFileTestHelper1(mounttmp):
+def fuseModifyFileTestHelperReadStartContents(mounttmp):
class Test(unittest.TestCase):
def runTest(self):
d1 = llfuse.listdir(mounttmp)
self.assertEqual("blub", f.read())
Test().runTest()
-def fuseModifyFileTestHelper2(mounttmp):
+def fuseModifyFileTestHelperReadEndContents(mounttmp):
class Test(unittest.TestCase):
def runTest(self):
d1 = llfuse.listdir(mounttmp)
with llfuse.lock:
m.new_collection(collection.api_response(), collection)
- self.pool.apply(fuseModifyFileTestHelper1, (self.mounttmp,))
+ self.pool.apply(fuseModifyFileTestHelperReadStartContents, (self.mounttmp,))
with collection.open("file1.txt", "w") as f:
f.write("plnp")
- self.pool.apply(fuseModifyFileTestHelper2, (self.mounttmp,))
+ self.pool.apply(fuseModifyFileTestHelperReadEndContents, (self.mounttmp,))
class FuseAddFileToCollectionTest(MountTestBase):
collection2 = self.api.collections().get(uuid=collection.manifest_locator()).execute()
self.assertRegexpMatches(collection2["manifest_text"],
- r'\. d41d8cd98f00b204e9800998ecf8427e\+0\+A[a-f0-9]{40}@[a-f0-9]{8} 0:0:file1\.txt$')
+ r'\. d41d8cd98f00b204e9800998ecf8427e\+0\+A\S+ 0:0:file1\.txt$')
-def fuseWriteFileTestHelper1(mounttmp):
+def fuseWriteFileTestHelperWriteFile(mounttmp):
class Test(unittest.TestCase):
def runTest(self):
with open(os.path.join(mounttmp, "file1.txt"), "w") as f:
f.write("Hello world!")
Test().runTest()
-def fuseWriteFileTestHelper2(mounttmp):
+def fuseWriteFileTestHelperReadFile(mounttmp):
class Test(unittest.TestCase):
def runTest(self):
with open(os.path.join(mounttmp, "file1.txt"), "r") as f:
self.assertNotIn("file1.txt", collection)
- self.pool.apply(fuseWriteFileTestHelper1, (self.mounttmp,))
+ self.assertEqual(0, self.operations.write_counter.get())
+ self.pool.apply(fuseWriteFileTestHelperWriteFile, (self.mounttmp,))
+ self.assertEqual(12, self.operations.write_counter.get())
with collection.open("file1.txt") as f:
self.assertEqual(f.read(), "Hello world!")
- self.pool.apply(fuseWriteFileTestHelper2, (self.mounttmp,))
+ self.assertEqual(0, self.operations.read_counter.get())
+ self.pool.apply(fuseWriteFileTestHelperReadFile, (self.mounttmp,))
+ self.assertEqual(12, self.operations.read_counter.get())
collection2 = self.api.collections().get(uuid=collection.manifest_locator()).execute()
self.assertRegexpMatches(collection2["manifest_text"],
- r'\. 86fb269d190d2c85f6e0468ceca42a20\+12\+A[a-f0-9]{40}@[a-f0-9]{8} 0:12:file1\.txt$')
+ r'\. 86fb269d190d2c85f6e0468ceca42a20\+12\+A\S+ 0:12:file1\.txt$')
def fuseUpdateFileTestHelper(mounttmp):
m.new_collection(collection.api_response(), collection)
self.assertTrue(m.writable())
- # See note in FuseWriteFileTest
+ # See note in MountTestBase.setUp
self.pool.apply(fuseUpdateFileTestHelper, (self.mounttmp,))
collection2 = self.api.collections().get(uuid=collection.manifest_locator()).execute()
self.assertRegexpMatches(collection2["manifest_text"],
- r'\. daaef200ebb921e011e3ae922dd3266b\+11\+A[a-f0-9]{40}@[a-f0-9]{8} 86fb269d190d2c85f6e0468ceca42a20\+12\+A[a-f0-9]{40}@[a-f0-9]{8} 0:11:file1\.txt 22:1:file1\.txt$')
+ r'\. daaef200ebb921e011e3ae922dd3266b\+11\+A\S+ 86fb269d190d2c85f6e0468ceca42a20\+12\+A\S+ 0:11:file1\.txt 22:1:file1\.txt$')
def fuseMkdirTestHelper(mounttmp):
collection2 = self.api.collections().get(uuid=collection.manifest_locator()).execute()
self.assertRegexpMatches(collection2["manifest_text"],
- r'\./testdir 86fb269d190d2c85f6e0468ceca42a20\+12\+A[a-f0-9]{40}@[a-f0-9]{8} 0:12:file1\.txt$')
+ r'\./testdir 86fb269d190d2c85f6e0468ceca42a20\+12\+A\S+ 0:12:file1\.txt$')
-def fuseRmTestHelper1(mounttmp):
+def fuseRmTestHelperWriteFile(mounttmp):
class Test(unittest.TestCase):
def runTest(self):
os.mkdir(os.path.join(mounttmp, "testdir"))
Test().runTest()
-def fuseRmTestHelper2(mounttmp):
+def fuseRmTestHelperDeleteFile(mounttmp):
class Test(unittest.TestCase):
def runTest(self):
# Can't delete because it's not empty
Test().runTest()
-def fuseRmTestHelper3(mounttmp):
+def fuseRmTestHelperRmdir(mounttmp):
class Test(unittest.TestCase):
def runTest(self):
# Should be able to delete now that it is empty
m.new_collection(collection.api_response(), collection)
self.assertTrue(m.writable())
- self.pool.apply(fuseRmTestHelper1, (self.mounttmp,))
+ self.pool.apply(fuseRmTestHelperWriteFile, (self.mounttmp,))
# Starting manifest
collection2 = self.api.collections().get(uuid=collection.manifest_locator()).execute()
self.assertRegexpMatches(collection2["manifest_text"],
- r'\./testdir 86fb269d190d2c85f6e0468ceca42a20\+12\+A[a-f0-9]{40}@[a-f0-9]{8} 0:12:file1\.txt$')
- self.pool.apply(fuseRmTestHelper2, (self.mounttmp,))
+ r'\./testdir 86fb269d190d2c85f6e0468ceca42a20\+12\+A\S+ 0:12:file1\.txt$')
+ self.pool.apply(fuseRmTestHelperDeleteFile, (self.mounttmp,))
# Can't have empty directories :-( so manifest will be empty.
collection2 = self.api.collections().get(uuid=collection.manifest_locator()).execute()
self.assertEqual(collection2["manifest_text"], "")
- self.pool.apply(fuseRmTestHelper3, (self.mounttmp,))
+ self.pool.apply(fuseRmTestHelperRmdir, (self.mounttmp,))
# manifest should be empty now.
collection2 = self.api.collections().get(uuid=collection.manifest_locator()).execute()
self.assertEqual(collection2["manifest_text"], "")
-def fuseMvFileTestHelper1(mounttmp):
+def fuseMvFileTestHelperWriteFile(mounttmp):
class Test(unittest.TestCase):
def runTest(self):
os.mkdir(os.path.join(mounttmp, "testdir"))
Test().runTest()
-def fuseMvFileTestHelper2(mounttmp):
+def fuseMvFileTestHelperMoveFile(mounttmp):
class Test(unittest.TestCase):
def runTest(self):
d1 = llfuse.listdir(os.path.join(mounttmp))
m.new_collection(collection.api_response(), collection)
self.assertTrue(m.writable())
- self.pool.apply(fuseMvFileTestHelper1, (self.mounttmp,))
+ self.pool.apply(fuseMvFileTestHelperWriteFile, (self.mounttmp,))
# Starting manifest
collection2 = self.api.collections().get(uuid=collection.manifest_locator()).execute()
self.assertRegexpMatches(collection2["manifest_text"],
- r'\./testdir 86fb269d190d2c85f6e0468ceca42a20\+12\+A[a-f0-9]{40}@[a-f0-9]{8} 0:12:file1\.txt$')
+ r'\./testdir 86fb269d190d2c85f6e0468ceca42a20\+12\+A\S+ 0:12:file1\.txt$')
- self.pool.apply(fuseMvFileTestHelper2, (self.mounttmp,))
+ self.pool.apply(fuseMvFileTestHelperMoveFile, (self.mounttmp,))
collection2 = self.api.collections().get(uuid=collection.manifest_locator()).execute()
self.assertRegexpMatches(collection2["manifest_text"],
- r'\. 86fb269d190d2c85f6e0468ceca42a20\+12\+A[a-f0-9]{40}@[a-f0-9]{8} 0:12:file1\.txt$')
+ r'\. 86fb269d190d2c85f6e0468ceca42a20\+12\+A\S+ 0:12:file1\.txt$')
def fuseRenameTestHelper(mounttmp):
# Starting manifest
collection2 = self.api.collections().get(uuid=collection.manifest_locator()).execute()
self.assertRegexpMatches(collection2["manifest_text"],
- r'\./testdir 86fb269d190d2c85f6e0468ceca42a20\+12\+A[a-f0-9]{40}@[a-f0-9]{8} 0:12:file1\.txt$')
+ r'\./testdir 86fb269d190d2c85f6e0468ceca42a20\+12\+A\S+ 0:12:file1\.txt$')
d1 = llfuse.listdir(os.path.join(self.mounttmp))
self.assertEqual(["testdir"], d1)
collection2 = self.api.collections().get(uuid=collection.manifest_locator()).execute()
self.assertRegexpMatches(collection2["manifest_text"],
- r'\./testdir2 86fb269d190d2c85f6e0468ceca42a20\+12\+A[a-f0-9]{40}@[a-f0-9]{8} 0:12:file1\.txt$')
+ r'\./testdir2 86fb269d190d2c85f6e0468ceca42a20\+12\+A\S+ 0:12:file1\.txt$')
class FuseUpdateFromEventTest(MountTestBase):
def runTest(self):
- arvados.logger.setLevel(logging.DEBUG)
-
collection = arvados.collection.Collection(api_client=self.api)
collection.save_new()
with llfuse.lock:
m.new_collection(collection.api_response(), collection)
- self.operations.listen_for_events(self.api)
+ self.operations.listen_for_events()
d1 = llfuse.listdir(os.path.join(self.mounttmp))
self.assertEqual([], sorted(d1))
self.assertEqual(f.read(), "bar")
self.assertRegexpMatches(d1[1],
- r'file1\.txt~conflict-\d\d\d\d-\d\d-\d\d-\d\d:\d\d:\d\d~')
+ r'file1\.txt~\d\d\d\d\d\d\d\d-\d\d\d\d\d\d~conflict~')
with open(os.path.join(mounttmp, d1[1]), "r") as f:
self.assertEqual(f.read(), "foo")
with collection2.open("file1.txt", "w") as f:
f.write("foo")
- # See comment in FuseWriteFileTest
+ # See note in MountTestBase.setUp
self.pool.apply(fuseFileConflictTestHelper, (self.mounttmp,))
with llfuse.lock:
m.new_collection(collection.api_response(), collection)
- # See comment in FuseWriteFileTest
+ # See note in MountTestBase.setUp
self.pool.apply(fuseUnlinkOpenFileTest, (self.mounttmp,))
self.assertEqual(collection.manifest_text(), "")
m = self.make_mount(fuse.MagicDirectory)
- # See comment in FuseWriteFileTest
+ # See note in MountTestBase.setUp
self.pool.apply(fuseMvFileBetweenCollectionsTest1, (self.mounttmp,
collection1.manifest_locator(),
collection2.manifest_locator()))
collection1.update()
collection2.update()
- self.assertRegexpMatches(collection1.manifest_text(), r"\. 86fb269d190d2c85f6e0468ceca42a20\+12\+A[a-f0-9]{40}@[a-f0-9]{8} 0:12:file1\.txt$")
+ self.assertRegexpMatches(collection1.manifest_text(), r"\. 86fb269d190d2c85f6e0468ceca42a20\+12\+A\S+ 0:12:file1\.txt$")
self.assertEqual(collection2.manifest_text(), "")
self.pool.apply(fuseMvFileBetweenCollectionsTest2, (self.mounttmp,
collection2.update()
self.assertEqual(collection1.manifest_text(), "")
- self.assertRegexpMatches(collection2.manifest_text(), r"\. 86fb269d190d2c85f6e0468ceca42a20\+12\+A[a-f0-9]{40}@[a-f0-9]{8} 0:12:file2\.txt$")
+ self.assertRegexpMatches(collection2.manifest_text(), r"\. 86fb269d190d2c85f6e0468ceca42a20\+12\+A\S+ 0:12:file2\.txt$")
+
+ collection1.stop_threads()
+ collection2.stop_threads()
def fuseMvDirBetweenCollectionsTest1(mounttmp, uuid1, uuid2):
m = self.make_mount(fuse.MagicDirectory)
- # See comment in FuseWriteFileTest
+ # See note in MountTestBase.setUp
self.pool.apply(fuseMvDirBetweenCollectionsTest1, (self.mounttmp,
collection1.manifest_locator(),
collection2.manifest_locator()))
collection1.update()
collection2.update()
- self.assertRegexpMatches(collection1.manifest_text(), r"\./testdir 86fb269d190d2c85f6e0468ceca42a20\+12\+A[a-f0-9]{40}@[a-f0-9]{8} 0:12:file1\.txt$")
+ self.assertRegexpMatches(collection1.manifest_text(), r"\./testdir 86fb269d190d2c85f6e0468ceca42a20\+12\+A\S+ 0:12:file1\.txt$")
self.assertEqual(collection2.manifest_text(), "")
self.pool.apply(fuseMvDirBetweenCollectionsTest2, (self.mounttmp,
collection2.update()
self.assertEqual(collection1.manifest_text(), "")
- self.assertRegexpMatches(collection2.manifest_text(), r"\./testdir2 86fb269d190d2c85f6e0468ceca42a20\+12\+A[a-f0-9]{40}@[a-f0-9]{8} 0:12:file1\.txt$")
+ self.assertRegexpMatches(collection2.manifest_text(), r"\./testdir2 86fb269d190d2c85f6e0468ceca42a20\+12\+A\S+ 0:12:file1\.txt$")
+ collection1.stop_threads()
+ collection2.stop_threads()
def fuseProjectMkdirTestHelper1(mounttmp):
class Test(unittest.TestCase):
self.pool.apply(fuseProjectMvTestHelper1, (self.mounttmp,))
+def fuseFsyncTestHelper(mounttmp, k):
+ class Test(unittest.TestCase):
+ def runTest(self):
+ fd = os.open(os.path.join(mounttmp, k), os.O_RDONLY)
+ os.fsync(fd)
+ os.close(fd)
+
+ Test().runTest()
+
+class FuseFsyncTest(FuseMagicTest):
+ def runTest(self):
+ self.make_mount(fuse.MagicDirectory)
+ self.pool.apply(fuseFsyncTestHelper, (self.mounttmp, self.testcollection))
+
+
+class MagicDirApiError(FuseMagicTest):
+ def setUp(self):
+ api = mock.MagicMock()
+ super(MagicDirApiError, self).setUp(api=api)
+ api.collections().get().execute.side_effect = iter([Exception('API fail'), {"manifest_text": self.test_manifest}])
+ api.keep.get.side_effect = Exception('Keep fail')
+
+ def runTest(self):
+ self.make_mount(fuse.MagicDirectory)
+
+ self.operations.inodes.inode_cache.cap = 1
+ self.operations.inodes.inode_cache.min_entries = 2
+
+ with self.assertRaises(OSError):
+ llfuse.listdir(os.path.join(self.mounttmp, self.testcollection))
+
+ llfuse.listdir(os.path.join(self.mounttmp, self.testcollection))
+
+
class FuseUnitTest(unittest.TestCase):
def test_sanitize_filename(self):
acceptable = [
self.assertEqual("_", fuse.sanitize_filename(""))
self.assertEqual("_", fuse.sanitize_filename("."))
self.assertEqual("__", fuse.sanitize_filename(".."))
+
+
+class FuseMagicTestPDHOnly(MountTestBase):
+ def setUp(self, api=None):
+ super(FuseMagicTestPDHOnly, self).setUp(api=api)
+
+ cw = arvados.CollectionWriter()
+
+ cw.start_new_file('thing1.txt')
+ cw.write("data 1")
+
+ self.testcollection = cw.finish()
+ self.test_manifest = cw.manifest_text()
+ created = self.api.collections().create(body={"manifest_text":self.test_manifest}).execute()
+ self.testcollectionuuid = str(created['uuid'])
+
+ def verify_pdh_only(self, pdh_only=False, skip_pdh_only=False):
+ if skip_pdh_only is True:
+ self.make_mount(fuse.MagicDirectory) # in this case, the default by_id applies
+ else:
+ self.make_mount(fuse.MagicDirectory, pdh_only=pdh_only)
+
+ mount_ls = llfuse.listdir(self.mounttmp)
+ self.assertIn('README', mount_ls)
+ self.assertFalse(any(arvados.util.keep_locator_pattern.match(fn) or
+ arvados.util.uuid_pattern.match(fn)
+ for fn in mount_ls),
+ "new FUSE MagicDirectory lists Collection")
+
+ # look up using pdh should succeed in all cases
+ self.assertDirContents(self.testcollection, ['thing1.txt'])
+ self.assertDirContents(os.path.join('by_id', self.testcollection),
+ ['thing1.txt'])
+ mount_ls = llfuse.listdir(self.mounttmp)
+ self.assertIn('README', mount_ls)
+ self.assertIn(self.testcollection, mount_ls)
+ self.assertIn(self.testcollection,
+ llfuse.listdir(os.path.join(self.mounttmp, 'by_id')))
+
+ files = {}
+ files[os.path.join(self.mounttmp, self.testcollection, 'thing1.txt')] = 'data 1'
+
+ for k, v in files.items():
+ with open(os.path.join(self.mounttmp, k)) as f:
+ self.assertEqual(v, f.read())
+
+ # look up using uuid should fail when pdh_only is set
+ if pdh_only is True:
+ with self.assertRaises(OSError):
+ self.assertDirContents(os.path.join('by_id', self.testcollectionuuid),
+ ['thing1.txt'])
+ else:
+ self.assertDirContents(os.path.join('by_id', self.testcollectionuuid),
+ ['thing1.txt'])
+
+ def test_with_pdh_only_true(self):
+ self.verify_pdh_only(pdh_only=True)
+
+ def test_with_pdh_only_false(self):
+ self.verify_pdh_only(pdh_only=False)
+
+ def test_with_default_by_id(self):
+ self.verify_pdh_only(skip_pdh_only=True)
+
+def _test_refresh_old_manifest(zzz):
+ fnm = 'zzzzz-8i9sb-0vsrcqi7whchuil.log.txt'
+ os.listdir(os.path.join(zzz))
+ time.sleep(3)
+ with open(os.path.join(zzz, fnm)) as f:
+ f.read()
+
+class TokenExpiryTest(MountTestBase):
+ def setUp(self):
+ super(TokenExpiryTest, self).setUp(local_store=False)
+
+ @mock.patch('arvados.keep.KeepClient.get')
+ def runTest(self, mocked_get):
+ self.api._rootDesc = {"blobSignatureTtl": 2}
+ mnt = self.make_mount(fuse.CollectionDirectory, collection_record='zzzzz-4zz18-op4e2lbej01tcvu')
+ mocked_get.return_value = 'fake data'
+
+ old_exp = int(time.time()) + 86400*14
+ self.pool.apply(_test_refresh_old_manifest, (self.mounttmp,))
+ want_exp = int(time.time()) + 86400*14
+
+ got_loc = mocked_get.call_args[0][0]
+ got_exp = int(
+ re.search(r'\+A[0-9a-f]+@([0-9a-f]+)', got_loc).group(1),
+ 16)
+ self.assertGreaterEqual(
+ got_exp, want_exp-1,
+ msg='now+2w = {:x}, but fuse fetched locator {} (old_exp {:x})'.format(
+ want_exp, got_loc, old_exp))
+ self.assertLessEqual(
+ got_exp, want_exp,
+ msg='server is not using the expected 2w TTL; test is ineffective')