Merge lp:~jderose/filestore/v1-on into lp:filestore

Proposed by Jason Gerard DeRose
Status: Merged
Merged at revision: 318
Proposed branch: lp:~jderose/filestore/v1-on
Merge into: lp:filestore
Diff against target: 1022 lines (+434/-88)
4 files modified
filestore/__init__.py (+147/-31)
filestore/misc.py (+10/-0)
filestore/tests/__init__.py (+257/-57)
filestore/tests/test_misc.py (+20/-0)
To merge this branch: bzr merge lp:~jderose/filestore/v1-on
Reviewer Review Type Date Requested Status
xzcvczx (community) Approve
dmedia Dev Pending
Review via email: mp+161334@code.launchpad.net

Description of the change

For details see this bug:

  https://bugs.launchpad.net/filestore/+bug/1174022

Changes:

  * Makes V1 the active protocol and updates all the related unit tests

  * Adds misc.write_files() helper that writes the test-vector files out into a directory, handy for unit testing things like the Hasher class against a specific protocol version

  * So it can support V0 and V1, Hasher.__init__() now takes optional "protocol" and "enc" kwargs (which default to `VERSION1` and `db32enc` respectively)

  * When FileStore.__init__() sees that ".dmedia/files/" contains a V0, Base32 layout, this directory is moved to ".dmedia/files0/" and the correct V1, Dbase32 layout is created in its place

  * And when above happens, if ".dmedia/store.json" exists it will be moved to ".dmedia/store0.json" and a new store.json file will written with the doc['_id'] re-encoded to Dbase32

  * Adds doodle `Migration` class that verifies files in "files0" according to their V0 ID, while re-hashing with V1 to calculate their new V1 ID

To post a comment you must log in.
lp:~jderose/filestore/v1-on updated
327. By Jason Gerard DeRose

Small tweak to Migration

Revision history for this message
xzcvczx (xzcvczx) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'filestore/__init__.py'
--- filestore/__init__.py 2013-04-22 06:23:41 +0000
+++ filestore/__init__.py 2013-04-28 20:23:24 +0000
@@ -35,7 +35,7 @@
35223522
36>>> ch = fs.hash_and_move(tmp_fp)36>>> ch = fs.hash_and_move(tmp_fp)
37>>> ch.id37>>> ch.id
38'MV2DIDJV66B7LCAXIAZRPSMN7I3LZJC6ANTODLJGZOZ3ZGTA'38'Y685HWMJEE5J39SLBEP4Y3WY7D8Y9JCIQYAFUFQ39MMV84EW'
39>>> ch.file_size39>>> ch.file_size
40224022
4141
@@ -77,16 +77,17 @@
77import io77import io
78import stat78import stat
79from base64 import b64encode79from base64 import b64encode
80import json
80from threading import Thread81from threading import Thread
81from queue import Queue82from queue import Queue
82from collections import namedtuple83from collections import namedtuple
83import logging84import logging
8485
85from dbase32 import random_id, DB32ALPHABET86from dbase32 import db32enc, isdb32, random_id, DB32ALPHABET
86from dbase32.rfc3548 import b32enc, isb3287from dbase32.rfc3548 import isb32, b32enc
8788
88from .protocols import TYPE_ERROR89from .protocols import TYPE_ERROR, VERSION0
89from .protocols import VERSION0 as PROTOCOL90from .protocols import VERSION1 as PROTOCOL
9091
91try:92try:
92 from _filestore import fallocate, fastread93 from _filestore import fallocate, fastread
@@ -157,8 +158,8 @@
157158
158 For example:159 For example:
159160
160 >>> b32enc(hash_leaf(2, b'XYZ'))161 >>> db32enc(hash_leaf(2, b'XYZ'))
161 'D7GIW5I5NB6SLJC5ALAX4WU7S7CNYUB3ULMECPY67FFQG4F7'162 'YLP5A3SDEEP69SSTFRK7J98AUBS7SW4E75RQN9D4HKRCUXWS'
162163
163 :param leaf_index: an ``int`` >= 0164 :param leaf_index: an ``int`` >= 0
164 :param leaf_data: optional ``bytes`` instance with contents of this leaf165 :param leaf_data: optional ``bytes`` instance with contents of this leaf
@@ -173,13 +174,13 @@
173 For example:174 For example:
174175
175 >>> hash_root(31415, b'NNNNNNNNNNNNNNNNNNNNNNNNNNNNNN')176 >>> hash_root(31415, b'NNNNNNNNNNNNNNNNNNNNNNNNNNNNNN')
176 'YFZNR7K6DENL77BAMXIYXJZ4VDRPQEYGKGIZT74M4UPLBBAO'177 'YFXKI7P3BFRMDBBLL4UDVHK3QTHGSXKU4VQ3SPJNKF96TBPV'
177178
178 :param file_size: an ``int`` >= 1179 :param file_size: an ``int`` >= 1
179 :param leaf_hashes: a ``bytes`` instance that is the concatenated leaf180 :param leaf_hashes: a ``bytes`` instance that is the concatenated leaf
180 hashes produced by `hash_leaf()`181 hashes produced by `hash_leaf()`
181 """182 """
182 return b32enc(PROTOCOL.hash_root(file_size, leaf_hashes))183 return db32enc(PROTOCOL.hash_root(file_size, leaf_hashes))
183184
184185
185class Hasher:186class Hasher:
@@ -187,9 +188,11 @@
187 A helper to keep track of state as you hash leaf after leaf.188 A helper to keep track of state as you hash leaf after leaf.
188 """189 """
189190
190 __slots__ = ('file_size', 'leaf_index', 'array', 'closed')191 __slots__ = ('protocol', 'enc', 'file_size', 'leaf_index', 'array', 'closed')
191192
192 def __init__(self):193 def __init__(self, protocol=PROTOCOL, enc=db32enc):
194 self.protocol = protocol
195 self.enc = enc
193 self.file_size = 0196 self.file_size = 0
194 self.leaf_index = 0197 self.leaf_index = 0
195 self.array = bytearray()198 self.array = bytearray()
@@ -206,7 +209,7 @@
206 )209 )
207 if len(leaf.data) < LEAF_SIZE:210 if len(leaf.data) < LEAF_SIZE:
208 self.closed = True211 self.closed = True
209 leaf_hash = PROTOCOL.hash_leaf(leaf.index, leaf.data)212 leaf_hash = self.protocol.hash_leaf(leaf.index, leaf.data)
210 self.array.extend(leaf_hash)213 self.array.extend(leaf_hash)
211 self.file_size += len(leaf.data)214 self.file_size += len(leaf.data)
212 self.leaf_index += 1215 self.leaf_index += 1
@@ -216,7 +219,7 @@
216 self.closed = True219 self.closed = True
217 leaf_hashes = bytes(self.array)220 leaf_hashes = bytes(self.array)
218 return ContentHash(221 return ContentHash(
219 b32enc(PROTOCOL.hash_root(self.file_size, leaf_hashes)),222 self.enc(self.protocol.hash_root(self.file_size, leaf_hashes)),
220 self.file_size,223 self.file_size,
221 leaf_hashes224 leaf_hashes
222 )225 )
@@ -319,26 +322,33 @@
319322
320def check_id(_id):323def check_id(_id):
321 """324 """
322 Verify that *_id* is a valid base32-encoded ID of the correct length.325 Verify that *_id* is a valid Dbase32 encoded ID of the correct length.
323326
324 A malicious *_id* could cause path traversal or other security gotchas,327 A malicious *_id* could cause path traversal or other security gotchas,
325 thus this sanity check. When *_id* is valid, it is returned unchanged:328 thus this sanity check. When *_id* is valid, it is returned unchanged:
326329
327 >>> check_id('OMPLTTYVTIJINDZWIS2PBZ4THWA6CTGCGT27RFIDKV7FSTCA')330 >>> check_id('39AY39AY39AY39AY39AY39AY39AY39AY39AY39AY39AY39AY')
328 'OMPLTTYVTIJINDZWIS2PBZ4THWA6CTGCGT27RFIDKV7FSTCA'331 '39AY39AY39AY39AY39AY39AY39AY39AY39AY39AY39AY39AY'
329332
330 However, when *_id* does not conform, an `IDError` is raised.333 However, if *_id* is the incorrect length, an `IDError` is raised:
331 raised:334
332335 >>> check_id('39AY39AY39AY39AY39AY39AY')
333 >>> check_id('NWBNVXVK5DQGIOW7MYR4K3KA')336 Traceback (most recent call last):
334 Traceback (most recent call last):337 ...
335 ...338 filestore.IDError: invalid file ID: '39AY39AY39AY39AY39AY39AY'
336 filestore.IDError: invalid file ID: 'NWBNVXVK5DQGIOW7MYR4K3KA'339
340 Likewise when *_id* is the correct length but contains symbols not included
341 in the Dbase32 alphabet:
342
343 >>> check_id('29AZ29AZ29AZ29AZ29AZ29AZ29AZ29AZ29AZ29AZ29AZ29AZ')
344 Traceback (most recent call last):
345 ...
346 filestore.IDError: invalid file ID: '29AZ29AZ29AZ29AZ29AZ29AZ29AZ29AZ29AZ29AZ29AZ29AZ'
337347
338 """348 """
339 if not isinstance(_id, str):349 if not isinstance(_id, str):
340 raise TypeError(TYPE_ERROR.format('_id', str, type(_id), _id))350 raise TypeError(TYPE_ERROR.format('_id', str, type(_id), _id))
341 if not (len(_id) == DIGEST_B32LEN and isb32(_id)):351 if not (len(_id) == DIGEST_B32LEN and isdb32(_id)):
342 raise IDError(_id)352 raise IDError(_id)
343 return _id353 return _id
344354
@@ -381,14 +391,14 @@
381 >>> check_root_hash(_id, 21, leaf_hash) # 21 instead of 22 bytes391 >>> check_root_hash(_id, 21, leaf_hash) # 21 instead of 22 bytes
382 Traceback (most recent call last):392 Traceback (most recent call last):
383 ...393 ...
384 filestore.RootHashError: 'MV2DIDJV66B7LCAXIAZRPSMN7I3LZJC6ANTODLJGZOZ3ZGTA'394 filestore.RootHashError: 'Y685HWMJEE5J39SLBEP4Y3WY7D8Y9JCIQYAFUFQ39MMV84EW'
385395
386 If the claimed *file_size* and *leaf_hashes* are correct, the 3 values are396 If the claimed *file_size* and *leaf_hashes* are correct, the 3 values are
387 returned in a `ContentHash` named tuple:397 returned in a `ContentHash` named tuple:
388398
389 >>> ch = check_root_hash(_id, 22, leaf_hash)399 >>> ch = check_root_hash(_id, 22, leaf_hash)
390 >>> ch.id400 >>> ch.id
391 'MV2DIDJV66B7LCAXIAZRPSMN7I3LZJC6ANTODLJGZOZ3ZGTA'401 'Y685HWMJEE5J39SLBEP4Y3WY7D8Y9JCIQYAFUFQ39MMV84EW'
392 >>> ch.file_size402 >>> ch.file_size
393 22403 22
394404
@@ -820,11 +830,103 @@
820 return StatVFS(size, used, avail, readonly, st.f_frsize)830 return StatVFS(size, used, avail, readonly, st.f_frsize)
821831
822832
833def is_v0_files(files):
834 for name in NAMES_DIFF:
835 if path.isdir(path.join(files, name)):
836 return True
837 return False
838
839
840def dumps(obj):
841 return json.dumps(obj,
842 ensure_ascii=False,
843 sort_keys=True,
844 separators=(',',': '),
845 indent=4,
846 )
847
848
849def migrate_store_doc(basedir):
850 store = path.join(basedir, 'store.json')
851 store0 = path.join(basedir, 'store0.json')
852 try:
853 doc = json.load(open(store, 'r'))
854 except FileNotFoundError:
855 log.error("'store.json' does not exist in %r", basedir)
856 return False
857
858 if path.exists(store0):
859 raise Exception("'store0.json' already exists in {!r}".format(basedir))
860
861 log.warning("Moving V0 'store.json' to 'store0.json' in %r", basedir)
862 os.rename(store, store0)
863
864 from dbase32.rfc3548 import b32dec
865 assert doc.get('migrated') is None
866 old_id = doc['_id']
867 new_id = db32enc(b32dec(old_id))
868 log.warning('Migrating FileStore ID from %r to %r in %r',
869 old_id, new_id, basedir)
870 doc['_id'] = new_id
871 doc['migrated'] = True
872 text = dumps(doc)
873 tmp = path.join(basedir, 'store.json.' + random_id())
874 fp = open(tmp, 'x')
875 fp.write(text)
876 fp.flush()
877 os.fsync(fp.fileno())
878 os.chmod(fp.fileno(), 0o444)
879 fp.close()
880 os.rename(tmp, store)
881 return True
882
883
884###################################################
885# The `Migration` class use for V0 => V1 migration:
886
887class Migration:
888 def __init__(self, fs):
889 assert isinstance(fs, FileStore)
890 self.fs = fs
891 self.files0 = fs.join('files0')
892 assert path.isdir(self.files0)
893
894 def __iter__(self):
895 for prefix in B32NAMES:
896 subdir = path.join(self.files0, prefix)
897 for name in sorted(os.listdir(subdir)):
898 src = path.join(subdir, name)
899 v0_id = prefix + name
900 assert path.isfile(src) or path.islink(src)
901 assert isb32(v0_id) and len(v0_id) == 48
902
903 if path.islink(src):
904 log.info('Reading symlink %r', src)
905 yield (v0_id, os.readlink(src), None)
906 else:
907 src_fp = open(src, 'rb')
908 h0 = Hasher(protocol=VERSION0, enc=b32enc)
909 h1 = Hasher()
910 for leaf in reader_iter(src_fp):
911 h0.hash_leaf(leaf)
912 h1.hash_leaf(leaf)
913 ch0 = h0.content_hash()
914 ch1 = h1.content_hash()
915 assert isdb32(ch1.id)
916 if ch0.id != v0_id:
917 yield (v0_id, None, None)
918 else:
919 dst = self.fs.path(ch1.id)
920 log.info('Moving %r to %r', src, dst)
921 os.rename(src, dst)
922 os.symlink(ch1.id, src)
923 yield (v0_id, ch1.id, ch1)
924
823925
824########################926########################
825# The `FileStore` class:927# The `FileStore` class:
826928
827class FileStore(object):929class FileStore:
828 """930 """
829 Arranges files in a special layout according to their content-hash.931 Arranges files in a special layout according to their content-hash.
830932
@@ -868,10 +970,24 @@
868 self.__class__.__name__, self.parentdir)970 self.__class__.__name__, self.parentdir)
869 )971 )
870972
973 files = self.join('files')
974 files0 = self.join('files0')
975
871 # If basedir doesn't exist, create it and initialize all dirs in layout:976 # If basedir doesn't exist, create it and initialize all dirs in layout:
872 if ensuredir(self.basedir):977 if ensuredir(self.basedir):
873 log.info('Initalizing FileStore in %r', self.basedir)978 log.info('Initalizing FileStore in %r', self.basedir)
874 self.init_dirs()979 self.init_dirs()
980 elif is_v0_files(files):
981 if path.exists(files0):
982 raise Exception(
983 "'files' is V0 layout but 'files0' exists in {!r}".format(self.basedir)
984 )
985 log.warning("Moving V0 'files' to 'files0' in %r", self.basedir)
986 os.rename(files, files0)
987 self.init_dirs()
988 migrate_store_doc(self.basedir)
989
990 self.needs_migration = path.isdir(files0)
875991
876 def __repr__(self):992 def __repr__(self):
877 return '{}({!r})'.format(self.__class__.__name__, self.parentdir)993 return '{}({!r})'.format(self.__class__.__name__, self.parentdir)
@@ -894,11 +1010,11 @@
894 * the entry is a symlink (even if to a valid file)1010 * the entry is a symlink (even if to a valid file)
895 * the file is zero bytes in size1011 * the file is zero bytes in size
896 """1012 """
897 for prefix in B32NAMES:1013 for prefix in DB32NAMES:
898 subdir = path.join(self.basedir, 'files', prefix)1014 subdir = path.join(self.basedir, 'files', prefix)
899 for name in sorted(os.listdir(subdir)):1015 for name in sorted(os.listdir(subdir)):
900 _id = prefix + name1016 _id = prefix + name
901 if len(_id) != DIGEST_B32LEN or not isb32(_id):1017 if len(_id) != DIGEST_B32LEN or not isdb32(_id):
902 continue1018 continue
903 fullname = path.join(subdir, name) 1019 fullname = path.join(subdir, name)
904 st = os.lstat(fullname)1020 st = os.lstat(fullname)
@@ -917,7 +1033,7 @@
917 d = path.join(self.basedir, name)1033 d = path.join(self.basedir, name)
918 ensuredir(d)1034 ensuredir(d)
919 os.chmod(d, 0o777)1035 os.chmod(d, 0o777)
920 for name in B32NAMES:1036 for name in DB32NAMES:
921 d = path.join(self.basedir, 'files', name)1037 d = path.join(self.basedir, 'files', name)
922 ensuredir(d)1038 ensuredir(d)
923 os.chmod(d, 0o777)1039 os.chmod(d, 0o777)
9241040
=== modified file 'filestore/misc.py'
--- filestore/misc.py 2013-02-28 23:50:17 +0000
+++ filestore/misc.py 2013-04-28 20:23:24 +0000
@@ -65,6 +65,16 @@
65 }65 }
6666
6767
68def write_files(tmpdir, protocol=VERSION1):
69 leaves = build_leaves(protocol.leaf_size)
70 for (key, data) in leaves.items():
71 open(path.join(tmpdir, key), 'xb').write(data)
72 fp = open(path.join(tmpdir, 'C' + key), 'xb')
73 fp.write(leaves['C'])
74 fp.write(data)
75 fp.close()
76
77
68def build_vectors(protocol, encoder=db32enc):78def build_vectors(protocol, encoder=db32enc):
69 leaves = build_leaves(protocol.leaf_size)79 leaves = build_leaves(protocol.leaf_size)
7080
7181
=== modified file 'filestore/tests/__init__.py'
--- filestore/tests/__init__.py 2013-02-21 15:43:24 +0000
+++ filestore/tests/__init__.py 2013-04-28 20:23:24 +0000
@@ -28,15 +28,15 @@
28from os import path28from os import path
29import io29import io
30import stat30import stat
31from base64 import b32encode, b32decode
32from subprocess import check_call31from subprocess import check_call
33import tempfile32import tempfile
34import shutil33import shutil
34import json
35from random import SystemRandom35from random import SystemRandom
3636
37from skein import skein51237from _skein import skein512
38from dbase32.rfc3548 import b32enc, b32dec38from dbase32 import isdb32, db32enc, random_id
39from dbase32 import isdb3239from dbase32.rfc3548 import b32enc
4040
41from filestore import protocols, misc41from filestore import protocols, misc
42import filestore42import filestore
@@ -82,8 +82,8 @@
82 return str(n).encode('utf-8')82 return str(n).encode('utf-8')
8383
8484
85def random_id(id_bytes=filestore.DIGEST_BYTES):85def random_file_id(numbytes=filestore.DIGEST_BYTES):
86 return b32encode(os.urandom(id_bytes)).decode('ascii')86 return random_id(numbytes)
8787
8888
89class TempDir(object):89class TempDir(object):
@@ -99,6 +99,11 @@
99 shutil.rmtree(self.dir)99 shutil.rmtree(self.dir)
100 self.dir = None100 self.dir = None
101101
102 def mkdir(self, *parts):
103 d = self.join(*parts)
104 os.mkdir(d)
105 return d
106
102 def makedirs(self, *parts):107 def makedirs(self, *parts):
103 d = self.join(*parts)108 d = self.join(*parts)
104 if not path.exists(d):109 if not path.exists(d):
@@ -410,23 +415,15 @@
410415
411 # Test with good values:416 # Test with good values:
412 content = b'N'417 content = b'N'
413 digest = f(2, content)
414 self.assertEqual(418 self.assertEqual(
415 digest,419 filestore.hash_leaf(2, content),
416 skein512(protocols.VERSION0._hash_leaf_index(b'2') + content,420 protocols.VERSION1.hash_leaf(2, content)
417 digest_bits=240,
418 pers=protocols.PERS_LEAF,
419 ).digest()
420 )421 )
421422
422 content = b'N' * filestore.LEAF_SIZE423 content = b'N' * filestore.LEAF_SIZE
423 digest = f(2, content)
424 self.assertEqual(424 self.assertEqual(
425 digest,425 filestore.hash_leaf(2, content),
426 skein512(protocols.VERSION0._hash_leaf_index(b'2') + content,426 protocols.VERSION1.hash_leaf(2, content)
427 digest_bits=240,
428 pers=protocols.PERS_LEAF,
429 ).digest()
430 )427 )
431428
432 # A 25k value sanity check on our crytographic claim that the429 # A 25k value sanity check on our crytographic claim that the
@@ -524,11 +521,11 @@
524 leaf_hashes = b'D' * filestore.DIGEST_BYTES521 leaf_hashes = b'D' * filestore.DIGEST_BYTES
525 self.assertEqual(522 self.assertEqual(
526 filestore.hash_root(1, leaf_hashes),523 filestore.hash_root(1, leaf_hashes),
527 '4AZOU4R7M6JKJJRQMVX42YB7ULRUCS6FZGJNZCDVOATXYPML'524 '39QLJTDIFYBSMR8A9IHAIGWMDCOX3TLWVKIAY9KSHGDGHCEL'
528 )525 )
529 self.assertEqual(526 self.assertEqual(
530 filestore.hash_root(filestore.LEAF_SIZE, leaf_hashes),527 filestore.hash_root(filestore.LEAF_SIZE, leaf_hashes),
531 'RXBEFCKXWKYNPXZOR234QHYI475L2AF7C4AOQUG7EG7UJBDJ'528 'JL57GWEV6OC4DGGE5UV4YRJ3J3ARVU8GDPO6TNEJAK9ULNJW'
532 )529 )
533530
534 # A 25k value sanity check on our crytographic claim that the531 # A 25k value sanity check on our crytographic claim that the
@@ -608,7 +605,7 @@
608 self.assertEqual(cm.exception.file_size, FILE_SIZE + 1)605 self.assertEqual(cm.exception.file_size, FILE_SIZE + 1)
609 self.assertEqual(cm.exception.leaf_hashes, LEAF_HASHES)606 self.assertEqual(cm.exception.leaf_hashes, LEAF_HASHES)
610 self.assertEqual(cm.exception.bad_id,607 self.assertEqual(cm.exception.bad_id,
611 'U5FK5XRT33ZJTCYIO3WJB7YTTGESXCBKZEW35J7FIQI7UN7S'608 'K7BCQ9MWWKIEPQ9FNSWUATRW8UC845H7HDESWTRIK8NPMIRT'
612 )609 )
613610
614 with self.assertRaises(filestore.RootHashError) as cm:611 with self.assertRaises(filestore.RootHashError) as cm:
@@ -617,7 +614,7 @@
617 self.assertEqual(cm.exception.file_size, FILE_SIZE - 1)614 self.assertEqual(cm.exception.file_size, FILE_SIZE - 1)
618 self.assertEqual(cm.exception.leaf_hashes, LEAF_HASHES)615 self.assertEqual(cm.exception.leaf_hashes, LEAF_HASHES)
619 self.assertEqual(cm.exception.bad_id,616 self.assertEqual(cm.exception.bad_id,
620 'OYESBWEZ4Y2AGSLMNZB4ZF75A2VG7NXVB4R25SSMRGXLN4CR'617 'EYYA6GQD64CR94H3OQEVX88S7R83CWBY8ECM4KRNCN4G7DC4'
621 )618 )
622619
623 def test_enumerate_leaf_hashes(self):620 def test_enumerate_leaf_hashes(self):
@@ -738,7 +735,7 @@
738735
739 def test_check_id(self):736 def test_check_id(self):
740 # Test with wrong type737 # Test with wrong type
741 bad = random_id(30).encode('utf-8')738 bad = random_file_id(30).encode('utf-8')
742 with self.assertRaises(TypeError) as cm:739 with self.assertRaises(TypeError) as cm:
743 filestore.check_id(bad)740 filestore.check_id(bad)
744 self.assertEqual(741 self.assertEqual(
@@ -757,7 +754,7 @@
757 self.assertIs(cm.exception.id, value)754 self.assertIs(cm.exception.id, value)
758755
759 # Test with 48 and 56 character:756 # Test with 48 and 56 character:
760 id48 = random_id(30)757 id48 = random_file_id(30)
761 self.assertIs(filestore.check_id(id48), id48)758 self.assertIs(filestore.check_id(id48), id48)
762759
763 # Test case sensitivity:760 # Test case sensitivity:
@@ -1200,7 +1197,7 @@
1200 ch = filestore.hash_fp(src_fp)1197 ch = filestore.hash_fp(src_fp)
1201 self.assertIsInstance(ch, filestore.ContentHash)1198 self.assertIsInstance(ch, filestore.ContentHash)
1202 self.assertEqual(ch.id,1199 self.assertEqual(ch.id,
1203 'DMJGE4OTWZVKSX426GHE46GZCGICMQOVWNGRVB7E665Y2RAM'1200 'CLSFQE444IGFX49Y9M6R9PLTF97HG7OQXAIEYYP54HNBN559'
1204 )1201 )
1205 self.assertEqual(ch.file_size, 20971520)1202 self.assertEqual(ch.file_size, 20971520)
1206 self.assertFalse(path.exists(dst))1203 self.assertFalse(path.exists(dst))
@@ -1210,7 +1207,7 @@
1210 ch = filestore.hash_fp(src_fp, dst_fp)1207 ch = filestore.hash_fp(src_fp, dst_fp)
1211 self.assertIsInstance(ch, filestore.ContentHash)1208 self.assertIsInstance(ch, filestore.ContentHash)
1212 self.assertEqual(ch.id,1209 self.assertEqual(ch.id,
1213 'DMJGE4OTWZVKSX426GHE46GZCGICMQOVWNGRVB7E665Y2RAM'1210 'CLSFQE444IGFX49Y9M6R9PLTF97HG7OQXAIEYYP54HNBN559'
1214 )1211 )
1215 self.assertEqual(ch.file_size, 20971520)1212 self.assertEqual(ch.file_size, 20971520)
1216 self.assertTrue(path.isfile(dst))1213 self.assertTrue(path.isfile(dst))
@@ -1224,7 +1221,7 @@
1224 tmp.write(data, L)1221 tmp.write(data, L)
1225 tmp.write(C + data, 'C' + L)1222 tmp.write(C + data, 'C' + L)
12261223
1227 vectors = misc.load_data('V0')1224 vectors = misc.load_data('V1')
1228 for name in ['A', 'B', 'C', 'CA', 'CB', 'CC']:1225 for name in ['A', 'B', 'C', 'CA', 'CB', 'CC']:
1229 src_fp = open(tmp.join(name), 'rb')1226 src_fp = open(tmp.join(name), 'rb')
1230 ch = filestore.hash_fp(src_fp)1227 ch = filestore.hash_fp(src_fp)
@@ -1290,10 +1287,105 @@
1290 filestore.StatVFS(size, size - free, avail, False, s1.f_frsize)1287 filestore.StatVFS(size, size - free, avail, False, s1.f_frsize)
1291 )1288 )
12921289
1290 def test_is_v0_files(self):
1291 v0 = TempDir()
1292 for name in filestore.B32NAMES:
1293 v0.mkdir(name)
1294 self.assertIs(filestore.is_v0_files(v0.dir), True)
1295 v1 = TempDir()
1296 for name in filestore.DB32NAMES:
1297 v1.mkdir(name)
1298 self.assertIs(filestore.is_v0_files(v1.dir), False)
1299 tmp = TempDir()
1300 fs = filestore.FileStore(tmp.dir)
1301 files = tmp.join('.dmedia', 'files')
1302 self.assertTrue(path.isdir(files))
1303 self.assertIs(filestore.is_v0_files(files), False)
1304
1305 def test_migrate_store_doc(self):
1306 tmp = TempDir()
1307 store = tmp.join('store.json')
1308 store0 = tmp.join('store0.json')
1309 self.assertIs(filestore.migrate_store_doc(tmp.dir), False)
1310
1311 # A V0 doc:
1312 doc = {
1313 '_id': 'DLA4NDZRW2LXEPF3RV7YHMON',
1314 'copies': 1,
1315 'plugin': 'filestore',
1316 'time': 1320063400.353743,
1317 'type': 'dmedia/store',
1318 }
1319 json.dump(doc, open(store, 'x'))
1320 self.assertIs(filestore.migrate_store_doc(tmp.dir), True)
1321 self.assertEqual(json.load(open(store, 'r')),
1322 {
1323 '_id': '6E3VG6SKPTEQ7I8UKOYRAFHG',
1324 'copies': 1,
1325 'plugin': 'filestore',
1326 'time': 1320063400.353743,
1327 'type': 'dmedia/store',
1328 'migrated': True,
1329 }
1330 )
1331 self.assertEqual(stat.S_IMODE(os.stat(store).st_mode), 0o444)
1332 self.assertEqual(json.load(open(store0, 'r')), doc)
1333 self.assertEqual(
1334 set(os.listdir(tmp.dir)),
1335 {'store.json', 'store0.json'}
1336 )
1337 with self.assertRaises(Exception) as cm:
1338 filestore.migrate_store_doc(tmp.dir)
1339 self.assertEqual(
1340 str(cm.exception),
1341 "'store0.json' already exists in {!r}".format(tmp.dir)
1342 )
1343
1344 # Try with some random ID values:
1345 for i in range(25):
1346 tmp = TempDir()
1347 store = tmp.join('store.json')
1348 store0 = tmp.join('store0.json')
1349 data = os.urandom(15)
1350 b32_id = b32enc(data)
1351 db32_id = db32enc(data)
1352 self.assertNotEqual(b32_id, db32_id)
1353 json.dump({'_id': b32_id}, open(store, 'x'))
1354 self.assertIs(filestore.migrate_store_doc(tmp.dir), True)
1355 self.assertEqual(json.load(open(store, 'r')),
1356 {
1357 '_id': db32_id,
1358 'migrated': True,
1359 }
1360 )
1361 self.assertEqual(stat.S_IMODE(os.stat(store).st_mode), 0o444)
1362 self.assertEqual(json.load(open(store0, 'r')), {'_id': b32_id})
1363 self.assertEqual(
1364 set(os.listdir(tmp.dir)),
1365 {'store.json', 'store0.json'}
1366 )
1367 with self.assertRaises(Exception) as cm:
1368 filestore.migrate_store_doc(tmp.dir)
1369 self.assertEqual(
1370 str(cm.exception),
1371 "'store0.json' already exists in {!r}".format(tmp.dir)
1372 )
1373
12931374
1294class TestHasher(TestCase):1375class TestHasher(TestCase):
1295 def test_init(self):1376 def test_init(self):
1296 h = filestore.Hasher()1377 h = filestore.Hasher()
1378 self.assertIs(h.protocol, protocols.VERSION1)
1379 self.assertIs(h.enc, db32enc)
1380 self.assertEqual(h.file_size, 0)
1381 self.assertEqual(h.leaf_index, 0)
1382 self.assertIsInstance(h.array, bytearray)
1383 self.assertEqual(h.array, b'')
1384 self.assertFalse(h.closed)
1385
1386 h = filestore.Hasher(protocols.VERSION0, b32enc)
1387 self.assertIs(h.protocol, protocols.VERSION0)
1388 self.assertIs(h.enc, b32enc)
1297 self.assertEqual(h.file_size, 0)1389 self.assertEqual(h.file_size, 0)
1298 self.assertEqual(h.leaf_index, 0)1390 self.assertEqual(h.leaf_index, 0)
1299 self.assertIsInstance(h.array, bytearray)1391 self.assertIsInstance(h.array, bytearray)
@@ -1334,7 +1426,7 @@
1334 str(cm.exception),1426 str(cm.exception),
1335 'Expected leaf.index 1, got 0'1427 'Expected leaf.index 1, got 0'
1336 )1428 )
1337 1429
1338 # Test when it's all good1430 # Test when it's all good
1339 h = filestore.Hasher()1431 h = filestore.Hasher()
13401432
@@ -1370,6 +1462,28 @@
1370 h.content_hash()1462 h.content_hash()
1371 self.assertTrue(h.closed)1463 self.assertTrue(h.closed)
13721464
1465 def test_V1(self):
1466 tmp = TempDir()
1467 root_hashes = misc.load_data('V1')['root_hashes']
1468 misc.write_files(tmp.dir, protocols.VERSION1)
1469 for (name, _id) in root_hashes.items():
1470 fp = open(tmp.join(name), 'rb')
1471 h = filestore.Hasher()
1472 for leaf in filestore.reader_iter(fp):
1473 h.hash_leaf(leaf)
1474 self.assertEqual(h.content_hash().id, _id)
1475
1476 def test_V0(self):
1477 tmp = TempDir()
1478 root_hashes = misc.load_data('V0')['root_hashes']
1479 misc.write_files(tmp.dir, protocols.VERSION0)
1480 for (name, _id) in root_hashes.items():
1481 fp = open(tmp.join(name), 'rb')
1482 h = filestore.Hasher(protocols.VERSION0, b32enc)
1483 for leaf in filestore.reader_iter(fp):
1484 h.hash_leaf(leaf)
1485 self.assertEqual(h.content_hash().id, _id)
1486
13731487
1374class TestFileStore(TestCase):1488class TestFileStore(TestCase):
1375 def test_init(self):1489 def test_init(self):
@@ -1438,12 +1552,14 @@
1438 self.assertTrue(path.isdir(fs.tmp))1552 self.assertTrue(path.isdir(fs.tmp))
1439 self.assertIsNone(fs.id)1553 self.assertIsNone(fs.id)
1440 self.assertEqual(fs.copies, 0)1554 self.assertEqual(fs.copies, 0)
1555 self.assertFalse(fs.needs_migration)
14411556
1442 # Test when _id and copies are supplied1557 # Test when _id and copies are supplied
1443 tmp = TempDir()1558 tmp = TempDir()
1444 fs = filestore.FileStore(tmp.dir, 'foo', 1)1559 fs = filestore.FileStore(tmp.dir, 'foo', 1)
1445 self.assertEqual(fs.id, 'foo')1560 self.assertEqual(fs.id, 'foo')
1446 self.assertEqual(fs.copies, 1)1561 self.assertEqual(fs.copies, 1)
1562 self.assertFalse(fs.needs_migration)
14471563
1448 # Test when basedir exists and is a directory1564 # Test when basedir exists and is a directory
1449 tmp = TempDir()1565 tmp = TempDir()
@@ -1454,6 +1570,7 @@
1454 self.assertTrue(path.isdir(basedir))1570 self.assertTrue(path.isdir(basedir))
1455 self.assertEqual(fs.tmp, path.join(basedir, 'tmp'))1571 self.assertEqual(fs.tmp, path.join(basedir, 'tmp'))
1456 self.assertFalse(path.isdir(fs.tmp))1572 self.assertFalse(path.isdir(fs.tmp))
1573 self.assertFalse(fs.needs_migration)
14571574
1458 # Test when basedir exists and is a file1575 # Test when basedir exists and is a file
1459 tmp = TempDir()1576 tmp = TempDir()
@@ -1477,6 +1594,89 @@
1477 '{!r} is symlink to {!r}'.format(basedir, d)1594 '{!r} is symlink to {!r}'.format(basedir, d)
1478 )1595 )
14791596
1597 # Test when .dmedia/files/ contains a V0, Base32 layout:
1598 tmp = TempDir()
1599 files = tmp.join('.dmedia', 'files')
1600 files0 = tmp.join('.dmedia', 'files0')
1601 fs = filestore.FileStore(tmp.dir)
1602 shutil.rmtree(files)
1603 os.mkdir(files)
1604 for name in filestore.B32NAMES:
1605 os.mkdir(path.join(files, name))
1606 fs = filestore.FileStore(tmp.dir)
1607 self.assertTrue(fs.needs_migration)
1608 self.assertTrue(path.isdir(files))
1609 for name in filestore.DB32NAMES:
1610 self.assertTrue(path.isdir(path.join(files, name)))
1611 self.assertEqual(sorted(os.listdir(files)), list(filestore.DB32NAMES))
1612 self.assertTrue(path.isdir(files0))
1613 for name in filestore.B32NAMES:
1614 self.assertTrue(path.isdir(path.join(files0, name)))
1615 self.assertEqual(sorted(os.listdir(files0)), list(filestore.B32NAMES))
1616
1617 # Test that no futher change is done:
1618 fs = filestore.FileStore(tmp.dir)
1619 self.assertTrue(fs.needs_migration)
1620 self.assertTrue(path.isdir(files))
1621 for name in filestore.DB32NAMES:
1622 self.assertTrue(path.isdir(path.join(files, name)))
1623 self.assertEqual(sorted(os.listdir(files)), list(filestore.DB32NAMES))
1624 self.assertTrue(path.isdir(files0))
1625 for name in filestore.B32NAMES:
1626 self.assertTrue(path.isdir(path.join(files0, name)))
1627 self.assertEqual(sorted(os.listdir(files0)), list(filestore.B32NAMES))
1628
1629 # Test when files contains V0/Base32 layout but files0 already exists:
1630 shutil.rmtree(files)
1631 os.mkdir(files)
1632 for name in filestore.B32NAMES:
1633 os.mkdir(path.join(files, name))
1634 with self.assertRaises(Exception) as cm:
1635 fs = filestore.FileStore(tmp.dir)
1636 self.assertEqual(
1637 str(cm.exception),
1638 "'files' is V0 layout but 'files0' exists in {!r}".format(tmp.join('.dmedia'))
1639 )
1640
1641 # Test that store.json gets properly migrated:
1642 tmp = TempDir()
1643 files = tmp.join('.dmedia', 'files')
1644 files0 = tmp.join('.dmedia', 'files0')
1645 store = tmp.join('.dmedia', 'store.json')
1646 store0 = tmp.join('.dmedia', 'store0.json')
1647
1648 # Setup:
1649 fs = filestore.FileStore(tmp.dir)
1650 shutil.rmtree(files)
1651 os.mkdir(files)
1652 for name in filestore.B32NAMES:
1653 os.mkdir(path.join(files, name))
1654 data = os.urandom(15)
1655 b32_id = b32enc(data)
1656 db32_id = db32enc(data)
1657 self.assertNotEqual(b32_id, db32_id)
1658 json.dump({'_id': b32_id}, open(store, 'x'))
1659
1660 # And test:
1661 fs = filestore.FileStore(tmp.dir)
1662 self.assertTrue(fs.needs_migration)
1663 self.assertTrue(path.isdir(files))
1664 for name in filestore.DB32NAMES:
1665 self.assertTrue(path.isdir(path.join(files, name)))
1666 self.assertEqual(sorted(os.listdir(files)), list(filestore.DB32NAMES))
1667 self.assertTrue(path.isdir(files0))
1668 for name in filestore.B32NAMES:
1669 self.assertTrue(path.isdir(path.join(files0, name)))
1670 self.assertEqual(sorted(os.listdir(files0)), list(filestore.B32NAMES))
1671 self.assertEqual(json.load(open(store, 'r')),
1672 {
1673 '_id': db32_id,
1674 'migrated': True,
1675 }
1676 )
1677 self.assertEqual(stat.S_IMODE(os.stat(store).st_mode), 0o444)
1678 self.assertEqual(json.load(open(store0, 'r')), {'_id': b32_id})
1679
1480 def test_repr(self):1680 def test_repr(self):
1481 tmp = TempDir()1681 tmp = TempDir()
1482 fs = filestore.FileStore(tmp.dir)1682 fs = filestore.FileStore(tmp.dir)
@@ -1487,7 +1687,7 @@
1487 fs = filestore.FileStore(tmp.dir)1687 fs = filestore.FileStore(tmp.dir)
14881688
1489 # Should ignore files with wrong ID length:1689 # Should ignore files with wrong ID length:
1490 short = tuple(random_id(25) for i in range(50))1690 short = tuple(random_file_id(25) for i in range(50))
1491 for _id in short:1691 for _id in short:
1492 f = fs.join('files', _id[:2], _id[2:])1692 f = fs.join('files', _id[:2], _id[2:])
1493 assert not path.exists(f)1693 assert not path.exists(f)
@@ -1495,7 +1695,7 @@
1495 os.chmod(f, stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH)1695 os.chmod(f, stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH)
1496 self.assertEqual(path.getsize(f), 7)1696 self.assertEqual(path.getsize(f), 7)
1497 self.assertEqual(list(fs), [])1697 self.assertEqual(list(fs), [])
1498 long = tuple(random_id(40) for i in range(50))1698 long = tuple(random_file_id(40) for i in range(50))
1499 for _id in long:1699 for _id in long:
1500 f = fs.join('files', _id[:2], _id[2:])1700 f = fs.join('files', _id[:2], _id[2:])
1501 assert not path.exists(f)1701 assert not path.exists(f)
@@ -1506,7 +1706,7 @@
15061706
1507 # Should ignore files invalid b32 letters:1707 # Should ignore files invalid b32 letters:
1508 for i in range(50):1708 for i in range(50):
1509 _id = random_id(25) + '1ABCDEFG' # 1 is not in B32ALPHABET1709 _id = random_file_id(25) + '1ABCDEFG' # 1 is not in B32ALPHABET
1510 assert len(_id) == filestore.DIGEST_B32LEN1710 assert len(_id) == filestore.DIGEST_B32LEN
1511 f = fs.join('files', _id[:2], _id[2:])1711 f = fs.join('files', _id[:2], _id[2:])
1512 assert not path.exists(f)1712 assert not path.exists(f)
@@ -1517,7 +1717,7 @@
15171717
1518 # Should ignore empty files:1718 # Should ignore empty files:
1519 for i in range(50):1719 for i in range(50):
1520 _id = random_id()1720 _id = random_file_id()
1521 f = fs.join('files', _id[:2], _id[2:])1721 f = fs.join('files', _id[:2], _id[2:])
1522 assert not path.exists(f)1722 assert not path.exists(f)
1523 open(f, 'wb').close()1723 open(f, 'wb').close()
@@ -1528,7 +1728,7 @@
15281728
1529 # Should ignore directories1729 # Should ignore directories
1530 for i in range(50):1730 for i in range(50):
1531 _id = random_id()1731 _id = random_file_id()
1532 d = fs.join('files', _id[:2], _id[2:])1732 d = fs.join('files', _id[:2], _id[2:])
1533 assert not path.exists(d)1733 assert not path.exists(d)
1534 os.mkdir(d)1734 os.mkdir(d)
@@ -1539,7 +1739,7 @@
1539 # Now add valid files in (48 character IDs)1739 # Now add valid files in (48 character IDs)
1540 stats = []1740 stats = []
1541 for i in range(2000):1741 for i in range(2000):
1542 _id = random_id(30)1742 _id = random_file_id(30)
1543 size = i + 11743 size = i + 1
1544 f = fs.path(_id)1744 f = fs.path(_id)
1545 assert not path.exists(f)1745 assert not path.exists(f)
@@ -1556,7 +1756,7 @@
1556 # Should ignore symlinks, even if to valid files1756 # Should ignore symlinks, even if to valid files
1557 # This makes sure os.lstat() is being used rather than os.stat() 1757 # This makes sure os.lstat() is being used rather than os.stat()
1558 for i in range(50):1758 for i in range(50):
1559 _id = random_id()1759 _id = random_file_id()
1560 link = fs.path(_id)1760 link = fs.path(_id)
1561 assert not path.exists(link)1761 assert not path.exists(link)
1562 file = fs.path(stats[i].id)1762 file = fs.path(stats[i].id)
@@ -1578,7 +1778,7 @@
15781778
1579 self.assertEqual(1779 self.assertEqual(
1580 sorted(os.listdir(path.join(basedir, 'files'))),1780 sorted(os.listdir(path.join(basedir, 'files'))),
1581 list(B32NAMES)1781 list(filestore.DB32NAMES)
1582 )1782 )
1583 for name in ['corrupt', 'partial', 'tmp']:1783 for name in ['corrupt', 'partial', 'tmp']:
1584 d = path.join(basedir, name)1784 d = path.join(basedir, name)
@@ -1588,7 +1788,7 @@
1588 d = path.join(basedir, name)1788 d = path.join(basedir, name)
1589 self.assertTrue(path.isdir(d))1789 self.assertTrue(path.isdir(d))
1590 self.assertFalse(path.islink(d))1790 self.assertFalse(path.islink(d))
1591 for name in B32NAMES:1791 for name in filestore.DB32NAMES:
1592 d = path.join(basedir, 'files', name)1792 d = path.join(basedir, 'files', name)
1593 self.assertTrue(path.isdir(d))1793 self.assertTrue(path.isdir(d))
1594 self.assertFalse(path.islink(d))1794 self.assertFalse(path.islink(d))
@@ -1612,7 +1812,7 @@
16121812
1613 # Test when some subdirs exist:1813 # Test when some subdirs exist:
1614 os.rmdir(path.join(basedir, 'tmp'))1814 os.rmdir(path.join(basedir, 'tmp'))
1615 for (i, name) in enumerate(B32NAMES):1815 for (i, name) in enumerate(filestore.DB32NAMES):
1616 if i % 3 == 0:1816 if i % 3 == 0:
1617 d = path.join(basedir, 'files', name)1817 d = path.join(basedir, 'files', name)
1618 self.assertIsNone(fs.init_dirs())1818 self.assertIsNone(fs.init_dirs())
@@ -1702,7 +1902,7 @@
1702 parentdir = tmp.makedirs('foo')1902 parentdir = tmp.makedirs('foo')
1703 fs = filestore.FileStore(parentdir)1903 fs = filestore.FileStore(parentdir)
17041904
1705 _id = random_id()1905 _id = random_file_id()
1706 self.assertEqual(1906 self.assertEqual(
1707 fs.path(_id),1907 fs.path(_id),
1708 tmp.join('foo', '.dmedia', 'files', _id[:2], _id[2:])1908 tmp.join('foo', '.dmedia', 'files', _id[:2], _id[2:])
@@ -1719,7 +1919,7 @@
1719 parentdir = tmp.makedirs('foo')1919 parentdir = tmp.makedirs('foo')
1720 fs = filestore.FileStore(parentdir)1920 fs = filestore.FileStore(parentdir)
17211921
1722 _id = random_id()1922 _id = random_file_id()
1723 self.assertEqual(1923 self.assertEqual(
1724 fs.partial_path(_id),1924 fs.partial_path(_id),
1725 tmp.join('foo', '.dmedia', 'partial', _id)1925 tmp.join('foo', '.dmedia', 'partial', _id)
@@ -1736,7 +1936,7 @@
1736 parentdir = tmp.makedirs('foo')1936 parentdir = tmp.makedirs('foo')
1737 fs = filestore.FileStore(parentdir)1937 fs = filestore.FileStore(parentdir)
17381938
1739 _id = random_id()1939 _id = random_file_id()
1740 self.assertEqual(1940 self.assertEqual(
1741 fs.corrupt_path(_id),1941 fs.corrupt_path(_id),
1742 tmp.join('foo', '.dmedia', 'corrupt', _id)1942 tmp.join('foo', '.dmedia', 'corrupt', _id)
@@ -1762,7 +1962,7 @@
1762 tmp = TempDir()1962 tmp = TempDir()
1763 fs = filestore.FileStore(tmp.dir)1963 fs = filestore.FileStore(tmp.dir)
17641964
1765 id1 = random_id()1965 id1 = random_file_id()
17661966
1767 # File doesn't exist1967 # File doesn't exist
1768 self.assertFalse(fs.exists(id1))1968 self.assertFalse(fs.exists(id1))
@@ -1772,13 +1972,13 @@
1772 self.assertTrue(fs.exists(id1))1972 self.assertTrue(fs.exists(id1))
17731973
1774 # Not file:1974 # Not file:
1775 id2 = random_id()1975 id2 = random_file_id()
1776 tmp.makedirs('.dmedia', 'files', id2[:2], id2[2:])1976 tmp.makedirs('.dmedia', 'files', id2[:2], id2[2:])
1777 self.assertTrue(path.isdir(fs.path(id2)))1977 self.assertTrue(path.isdir(fs.path(id2)))
1778 self.assertFalse(fs.exists(id2))1978 self.assertFalse(fs.exists(id2))
17791979
1780 # Empty file1980 # Empty file
1781 id3 = random_id()1981 id3 = random_file_id()
1782 f = fs.path(id3)1982 f = fs.path(id3)
1783 assert not path.exists(f)1983 assert not path.exists(f)
1784 open(f, 'wb').close()1984 open(f, 'wb').close()
@@ -1789,7 +1989,7 @@
1789 # File doesn't exist1989 # File doesn't exist
1790 tmp = TempDir()1990 tmp = TempDir()
1791 fs = filestore.FileStore(tmp.dir)1991 fs = filestore.FileStore(tmp.dir)
1792 _id = random_id()1992 _id = random_file_id()
1793 with self.assertRaises(filestore.FileNotFound) as cm:1993 with self.assertRaises(filestore.FileNotFound) as cm:
1794 st = fs.stat(_id)1994 st = fs.stat(_id)
1795 self.assertEqual(cm.exception.id, _id)1995 self.assertEqual(cm.exception.id, _id)
@@ -1802,9 +2002,9 @@
1802 # File is a symlink:2002 # File is a symlink:
1803 tmp = TempDir()2003 tmp = TempDir()
1804 fs = filestore.FileStore(tmp.dir)2004 fs = filestore.FileStore(tmp.dir)
1805 file = random_id()2005 file = random_file_id()
1806 link = random_id()2006 link = random_file_id()
1807 open(fs.path(file), 'wb').write(b'Novacut')2007 open(fs.path(file), 'xb').write(b'Novacut')
1808 os.symlink(fs.path(file), fs.path(link))2008 os.symlink(fs.path(file), fs.path(link))
1809 assert path.isfile(fs.path(link))2009 assert path.isfile(fs.path(link))
1810 assert path.islink(fs.path(link))2010 assert path.islink(fs.path(link))
@@ -1820,7 +2020,7 @@
1820 # File is a directory2020 # File is a directory
1821 tmp = TempDir()2021 tmp = TempDir()
1822 fs = filestore.FileStore(tmp.dir)2022 fs = filestore.FileStore(tmp.dir)
1823 _id = random_id()2023 _id = random_file_id()
1824 os.mkdir(fs.path(_id))2024 os.mkdir(fs.path(_id))
1825 assert path.isdir(fs.path(_id))2025 assert path.isdir(fs.path(_id))
1826 with self.assertRaises(filestore.FileNotFound) as cm:2026 with self.assertRaises(filestore.FileNotFound) as cm:
@@ -1835,7 +2035,7 @@
1835 # Empty file2035 # Empty file
1836 tmp = TempDir()2036 tmp = TempDir()
1837 fs = filestore.FileStore(tmp.dir)2037 fs = filestore.FileStore(tmp.dir)
1838 _id = random_id()2038 _id = random_file_id()
1839 open(fs.path(_id), 'wb').close()2039 open(fs.path(_id), 'wb').close()
1840 assert path.isfile(fs.path(_id))2040 assert path.isfile(fs.path(_id))
1841 assert not path.islink(fs.path(_id))2041 assert not path.islink(fs.path(_id))
@@ -1852,7 +2052,7 @@
1852 # Valid file2052 # Valid file
1853 tmp = TempDir()2053 tmp = TempDir()
1854 fs = filestore.FileStore(tmp.dir)2054 fs = filestore.FileStore(tmp.dir)
1855 _id = random_id()2055 _id = random_file_id()
1856 open(fs.path(_id), 'wb').write(b'Novacut')2056 open(fs.path(_id), 'wb').write(b'Novacut')
1857 st = fs.stat(_id)2057 st = fs.stat(_id)
1858 self.assertIsInstance(st, filestore.Stat)2058 self.assertIsInstance(st, filestore.Stat)
@@ -1865,7 +2065,7 @@
1865 tmp = TempDir()2065 tmp = TempDir()
1866 fs = filestore.FileStore(tmp.dir)2066 fs = filestore.FileStore(tmp.dir)
18672067
1868 _id = random_id()2068 _id = random_file_id()
18692069
1870 # File doesn't exist2070 # File doesn't exist
1871 with self.assertRaises(filestore.FileNotFound) as cm:2071 with self.assertRaises(filestore.FileNotFound) as cm:
@@ -2123,7 +2323,7 @@
2123 def test_remove(self):2323 def test_remove(self):
2124 tmp = TempDir()2324 tmp = TempDir()
2125 fs = filestore.FileStore(tmp.dir)2325 fs = filestore.FileStore(tmp.dir)
2126 _id = random_id()2326 _id = random_file_id()
2127 canonical = fs.path(_id)2327 canonical = fs.path(_id)
21282328
2129 # File doesn't exist2329 # File doesn't exist
@@ -2259,7 +2459,7 @@
2259 def test_allocate_partial(self):2459 def test_allocate_partial(self):
2260 tmp = TempDir()2460 tmp = TempDir()
2261 fs = filestore.FileStore(tmp.dir)2461 fs = filestore.FileStore(tmp.dir)
2262 _id = random_id()2462 _id = random_file_id()
2263 filename = tmp.join('.dmedia', 'partial', _id)2463 filename = tmp.join('.dmedia', 'partial', _id)
22642464
2265 # Test when file dosen't yet exist2465 # Test when file dosen't yet exist
@@ -2294,7 +2494,7 @@
2294 self.assertEqual(os.fstat(fp.fileno()).st_size, 2311)2494 self.assertEqual(os.fstat(fp.fileno()).st_size, 2311)
2295 self.assertEqual(stat.S_IMODE(os.fstat(fp.fileno()).st_mode), 0o666)2495 self.assertEqual(stat.S_IMODE(os.fstat(fp.fileno()).st_mode), 0o666)
22962496
2297 _id = random_id() # We'll use a new ID for below2497 _id = random_file_id() # We'll use a new ID for below
2298 filename = tmp.join('.dmedia', 'partial', _id)2498 filename = tmp.join('.dmedia', 'partial', _id)
22992499
2300 # Test with bad size type:2500 # Test with bad size type:
@@ -2331,7 +2531,7 @@
2331 def test_move_to_canonical(self):2531 def test_move_to_canonical(self):
2332 tmp = TempDir()2532 tmp = TempDir()
2333 fs = filestore.FileStore(tmp.dir)2533 fs = filestore.FileStore(tmp.dir)
2334 _id = random_id()2534 _id = random_file_id()
2335 dst = fs.path(_id)2535 dst = fs.path(_id)
23362536
2337 # Test with wrong src_fp type2537 # Test with wrong src_fp type
@@ -2402,7 +2602,7 @@
2402 def test_move_to_corrupt(self):2602 def test_move_to_corrupt(self):
2403 tmp = TempDir()2603 tmp = TempDir()
2404 fs = filestore.FileStore(tmp.dir)2604 fs = filestore.FileStore(tmp.dir)
2405 _id = random_id()2605 _id = random_file_id()
2406 corrupt = fs.corrupt_path(_id)2606 corrupt = fs.corrupt_path(_id)
2407 canonical = fs.path(_id)2607 canonical = fs.path(_id)
24082608
24092609
=== modified file 'filestore/tests/test_misc.py'
--- filestore/tests/test_misc.py 2013-03-01 00:02:29 +0000
+++ filestore/tests/test_misc.py 2013-04-28 20:23:24 +0000
@@ -33,6 +33,8 @@
33from dbase32 import db32enc, db32dec33from dbase32 import db32enc, db32dec
34from dbase32.rfc3548 import b32enc, b32dec34from dbase32.rfc3548 import b32enc, b32dec
3535
36from . import TempDir
37
36import filestore38import filestore
37from filestore.protocols import MIN_LEAF_SIZE39from filestore.protocols import MIN_LEAF_SIZE
38from filestore import misc, protocols40from filestore import misc, protocols
@@ -79,6 +81,24 @@
79 self.assertEqual(len(obj['B']), 2 * MIN_LEAF_SIZE - 1)81 self.assertEqual(len(obj['B']), 2 * MIN_LEAF_SIZE - 1)
80 self.assertEqual(len(obj['C']), 2 * MIN_LEAF_SIZE)82 self.assertEqual(len(obj['C']), 2 * MIN_LEAF_SIZE)
8183
84 def test_build_leaves(self):
85 tmp = TempDir()
86 leaves = misc.build_leaves(protocols.VERSION1.leaf_size)
87 self.assertIsNone(misc.write_files(tmp.dir))
88 self.assertEqual(
89 sorted(os.listdir(tmp.dir)),
90 ['A', 'B', 'C', 'CA', 'CB', 'CC']
91 )
92 for (key, data) in leaves.items():
93 self.assertEqual(
94 open(tmp.join(key), 'rb').read(),
95 data
96 )
97 self.assertEqual(
98 open(tmp.join('C' + key), 'rb').read(),
99 leaves['C'] + data
100 )
101
82 def test_tohex(self):102 def test_tohex(self):
83 for i in range(10):103 for i in range(10):
84 h = md5(os.urandom(16))104 h = md5(os.urandom(16))

Subscribers

People subscribed via source and target branches