filesystem_storage_test.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498
  1. package filesystem
  2. import (
  3. "context"
  4. "path/filepath"
  5. "reflect"
  6. "sort"
  7. "testing"
  8. "time"
  9. "github.com/stretchr/testify/require"
  10. "github.com/kopia/kopia/internal/blobtesting"
  11. "github.com/kopia/kopia/internal/gather"
  12. "github.com/kopia/kopia/internal/providervalidation"
  13. "github.com/kopia/kopia/internal/testlogging"
  14. "github.com/kopia/kopia/internal/testutil"
  15. "github.com/kopia/kopia/repo/blob"
  16. "github.com/kopia/kopia/repo/blob/sharded"
  17. )
  18. func TestFileStorage(t *testing.T) {
  19. t.Parallel()
  20. ctx := testlogging.Context(t)
  21. // Test various shard configurations.
  22. for _, shardSpec := range [][]int{
  23. {0},
  24. {1},
  25. {3, 3},
  26. {2},
  27. {1, 1},
  28. {1, 2},
  29. {2, 2, 2},
  30. } {
  31. path := testutil.TempDirectory(t)
  32. newctx, cancel := context.WithCancel(ctx)
  33. // use context that gets canceled after opening storage to ensure it's not used beyond New().
  34. r, err := New(newctx, &Options{
  35. Path: path,
  36. Options: sharded.Options{
  37. DirectoryShards: shardSpec,
  38. },
  39. }, true)
  40. cancel()
  41. require.NoError(t, err)
  42. require.NotNil(t, r)
  43. blobtesting.VerifyStorage(ctx, t, r, blob.PutOptions{})
  44. blobtesting.AssertConnectionInfoRoundTrips(ctx, t, r)
  45. require.NoError(t, r.Close(ctx))
  46. }
  47. }
  48. func TestFileStorageValidate(t *testing.T) {
  49. t.Parallel()
  50. testutil.ProviderTest(t)
  51. ctx := testlogging.Context(t)
  52. path := testutil.TempDirectory(t)
  53. r, err := New(ctx, &Options{
  54. Path: path,
  55. Options: sharded.Options{},
  56. }, true)
  57. require.NoError(t, err)
  58. require.NotNil(t, r)
  59. blobtesting.VerifyStorage(ctx, t, r, blob.PutOptions{})
  60. blobtesting.AssertConnectionInfoRoundTrips(ctx, t, r)
  61. require.NoError(t, providervalidation.ValidateProvider(ctx, r, blobtesting.TestValidationOptions))
  62. require.NoError(t, r.Close(ctx))
  63. }
  64. const (
  65. t1 = "392ee1bc299db9f235e046a62625afb84902"
  66. t2 = "2a7ff4f29eddbcd4c18fa9e73fec20bbb71f"
  67. t3 = "0dae5918f83e6a24c8b3e274ca1026e43f24"
  68. )
  69. func TestFileStorageTouch(t *testing.T) {
  70. t.Parallel()
  71. ctx := testlogging.Context(t)
  72. path := testutil.TempDirectory(t)
  73. r, err := New(ctx, &Options{
  74. Path: path,
  75. }, true)
  76. if r == nil || err != nil {
  77. t.Errorf("unexpected result: %v %v", r, err)
  78. }
  79. fs := testutil.EnsureType[*fsStorage](t, r)
  80. assertNoError(t, fs.PutBlob(ctx, t1, gather.FromSlice([]byte{1}), blob.PutOptions{}))
  81. time.Sleep(2 * time.Second) // sleep a bit to accommodate Apple filesystems with low timestamp resolution
  82. assertNoError(t, fs.PutBlob(ctx, t2, gather.FromSlice([]byte{1}), blob.PutOptions{}))
  83. time.Sleep(2 * time.Second)
  84. assertNoError(t, fs.PutBlob(ctx, t3, gather.FromSlice([]byte{1}), blob.PutOptions{}))
  85. time.Sleep(2 * time.Second) // sleep a bit to accommodate Apple filesystems with low timestamp resolution
  86. verifyBlobTimestampOrder(t, fs, t1, t2, t3)
  87. _, err = fs.TouchBlob(ctx, t2, 1*time.Hour)
  88. assertNoError(t, err) // has no effect, all timestamps are very new
  89. verifyBlobTimestampOrder(t, fs, t1, t2, t3)
  90. time.Sleep(2 * time.Second) // sleep a bit to accommodate Apple filesystems with low timestamp resolution
  91. _, err = fs.TouchBlob(ctx, t1, 0)
  92. assertNoError(t, err) // moves t1 to the top of the pile
  93. verifyBlobTimestampOrder(t, fs, t2, t3, t1)
  94. time.Sleep(2 * time.Second) // sleep a bit to accommodate Apple filesystems with low timestamp resolution
  95. _, err = fs.TouchBlob(ctx, t2, 0)
  96. assertNoError(t, err) // moves t2 to the top of the pile
  97. verifyBlobTimestampOrder(t, fs, t3, t1, t2)
  98. time.Sleep(2 * time.Second) // sleep a bit to accommodate Apple filesystems with low timestamp resolution
  99. _, err = fs.TouchBlob(ctx, t1, 0)
  100. assertNoError(t, err) // moves t1 to the top of the pile
  101. verifyBlobTimestampOrder(t, fs, t3, t2, t1)
  102. }
  103. func TestFileStorageConcurrency(t *testing.T) {
  104. t.Parallel()
  105. testutil.ProviderTest(t)
  106. path := testutil.TempDirectory(t)
  107. ctx := testlogging.Context(t)
  108. st, err := New(ctx, &Options{
  109. Path: path,
  110. }, true)
  111. require.NoError(t, err)
  112. blobtesting.VerifyConcurrentAccess(t, st, blobtesting.ConcurrentAccessOptions{
  113. NumBlobs: 16,
  114. Getters: 8,
  115. Putters: 8,
  116. Deleters: 8,
  117. Listers: 8,
  118. Iterations: 500,
  119. RangeGetPercentage: 10,
  120. NonExistentListPrefixPercentage: 10,
  121. })
  122. }
  123. func TestFilesystemStorageDirectoryShards(t *testing.T) {
  124. t.Parallel()
  125. ctx := testlogging.Context(t)
  126. dataDir := testutil.TempDirectory(t)
  127. st, err := New(ctx, &Options{
  128. Path: dataDir,
  129. Options: sharded.Options{
  130. DirectoryShards: []int{5, 2},
  131. },
  132. }, true)
  133. require.NoError(t, err)
  134. defer st.Close(ctx)
  135. require.NoError(t, st.PutBlob(ctx, "someblob1234567812345678", gather.FromSlice([]byte{1, 2, 3}), blob.PutOptions{}))
  136. require.FileExists(t, filepath.Join(dataDir, "someb", "lo", "b1234567812345678.f"))
  137. }
  138. func TestFileStorage_GetBlob_RetriesOnReadError(t *testing.T) {
  139. t.Parallel()
  140. ctx := testlogging.Context(t)
  141. dataDir := testutil.TempDirectory(t)
  142. osi := newMockOS()
  143. osi.readFileRemainingErrors.Store(1)
  144. st, err := New(ctx, &Options{
  145. Path: dataDir,
  146. Options: sharded.Options{
  147. DirectoryShards: []int{5, 2},
  148. },
  149. osInterfaceOverride: osi,
  150. }, true)
  151. require.NoError(t, err)
  152. defer st.Close(ctx)
  153. require.NoError(t, st.PutBlob(ctx, "someblob1234567812345678", gather.FromSlice([]byte{1, 2, 3}), blob.PutOptions{}))
  154. var buf gather.WriteBuffer
  155. defer buf.Close()
  156. require.NoError(t, st.GetBlob(ctx, "someblob1234567812345678", 1, 2, &buf))
  157. require.Equal(t, []byte{2, 3}, buf.ToByteSlice())
  158. }
  159. func TestFileStorage_GetMetadata_RetriesOnError(t *testing.T) {
  160. t.Parallel()
  161. ctx := testlogging.Context(t)
  162. dataDir := testutil.TempDirectory(t)
  163. osi := newMockOS()
  164. osi.statRemainingErrors.Store(1)
  165. st, err := New(ctx, &Options{
  166. Path: dataDir,
  167. Options: sharded.Options{
  168. DirectoryShards: []int{5, 2},
  169. },
  170. }, true)
  171. require.NoError(t, err)
  172. defer st.Close(ctx)
  173. require.NoError(t, st.PutBlob(ctx, "someblob1234567812345678", gather.FromSlice([]byte{1, 2, 3}), blob.PutOptions{}))
  174. asFsImpl(t, st).osi = osi
  175. _, err = st.GetMetadata(ctx, "someblob1234567812345678")
  176. require.NoError(t, err)
  177. }
  178. func asFsImpl(t *testing.T, st blob.Storage) *fsImpl {
  179. t.Helper()
  180. fsSt := testutil.EnsureType[*fsStorage](t, st)
  181. return testutil.EnsureType[*fsImpl](t, fsSt.Impl)
  182. }
  183. func TestFileStorage_PutBlob_RetriesOnErrors(t *testing.T) {
  184. t.Parallel()
  185. ctx := testlogging.Context(t)
  186. dataDir := testutil.TempDirectory(t)
  187. osi := newMockOS()
  188. osi.createNewFileRemainingErrors.Store(3)
  189. osi.mkdirAllRemainingErrors.Store(2)
  190. osi.writeFileRemainingErrors.Store(3)
  191. osi.writeFileCloseRemainingErrors.Store(2)
  192. osi.renameRemainingErrors.Store(1)
  193. osi.removeRemainingRetriableErrors.Store(3)
  194. osi.chownRemainingErrors.Store(3)
  195. osi.chtimesRemainingErrors.Store(3)
  196. fileUID := 3
  197. fileGID := 4
  198. st, err := New(ctx, &Options{
  199. Path: dataDir,
  200. FileUID: &fileUID,
  201. FileGID: &fileGID,
  202. Options: sharded.Options{
  203. DirectoryShards: []int{5, 2},
  204. },
  205. }, true)
  206. require.NoError(t, err)
  207. asFsImpl(t, st).osi = osi
  208. defer st.Close(ctx)
  209. require.NoError(t, st.PutBlob(ctx, "someblob1234567812345678", gather.FromSlice([]byte{1, 2, 3}), blob.PutOptions{}))
  210. var buf gather.WriteBuffer
  211. defer buf.Close()
  212. require.NoError(t, st.GetBlob(ctx, "someblob1234567812345678", 1, 2, &buf))
  213. require.Equal(t, []byte{2, 3}, buf.ToByteSlice())
  214. var mt time.Time
  215. require.NoError(t, st.PutBlob(ctx, "someblob1234567812345678", gather.FromSlice([]byte{1, 2, 3}), blob.PutOptions{
  216. GetModTime: &mt,
  217. }))
  218. require.NoError(t, st.PutBlob(ctx, "someblob1234567812345678", gather.FromSlice([]byte{1, 2, 3}), blob.PutOptions{
  219. SetModTime: time.Date(2020, 1, 1, 12, 0, 0, 0, time.UTC),
  220. }))
  221. }
  222. func TestFileStorage_DeleteBlob_ErrorHandling(t *testing.T) {
  223. t.Parallel()
  224. ctx := testlogging.Context(t)
  225. dataDir := testutil.TempDirectory(t)
  226. osi := newMockOS()
  227. osi.removeRemainingNonRetriableErrors.Store(1)
  228. st, err := New(ctx, &Options{
  229. Path: dataDir,
  230. Options: sharded.Options{
  231. DirectoryShards: []int{5, 2},
  232. },
  233. }, true)
  234. require.NoError(t, err)
  235. asFsImpl(t, st).osi = osi
  236. defer st.Close(ctx)
  237. require.ErrorIs(t, st.DeleteBlob(ctx, "someblob1234567812345678"), errNonRetriable)
  238. }
  239. func TestFileStorage_New_MkdirAllFailureIsIgnored(t *testing.T) {
  240. t.Parallel()
  241. ctx := testlogging.Context(t)
  242. dataDir := testutil.TempDirectory(t)
  243. osi := newMockOS()
  244. osi.mkdirAllRemainingErrors.Store(1)
  245. st, err := New(ctx, &Options{
  246. Path: dataDir,
  247. Options: sharded.Options{
  248. DirectoryShards: []int{5, 2},
  249. },
  250. osInterfaceOverride: osi,
  251. }, true)
  252. require.NoError(t, err)
  253. st.Close(ctx)
  254. }
  255. func TestFileStorage_New_ChecksDirectoryExistence(t *testing.T) {
  256. t.Parallel()
  257. ctx := testlogging.Context(t)
  258. dataDir := testutil.TempDirectory(t)
  259. osi := newMockOS()
  260. osi.statRemainingErrors.Store(1)
  261. st, err := New(ctx, &Options{
  262. Path: dataDir,
  263. Options: sharded.Options{
  264. DirectoryShards: []int{5, 2},
  265. },
  266. osInterfaceOverride: osi,
  267. }, true)
  268. require.Error(t, err)
  269. require.Nil(t, st)
  270. }
  271. func TestFileStorage_ListBlobs_ErrorHandling(t *testing.T) {
  272. t.Parallel()
  273. ctx := testlogging.Context(t)
  274. dataDir := testutil.TempDirectory(t)
  275. osi := newMockOS()
  276. osi.readDirRemainingErrors.Store(3)
  277. osi.readDirRemainingFileDeletedDirEntry.Store(3)
  278. st, err := New(ctx, &Options{
  279. Path: dataDir,
  280. Options: sharded.Options{
  281. DirectoryShards: []int{5, 2},
  282. },
  283. }, true)
  284. require.NoError(t, err)
  285. asFsImpl(t, st).osi = osi
  286. defer st.Close(ctx)
  287. require.NoError(t, st.ListBlobs(ctx, "", func(bm blob.Metadata) error {
  288. return nil
  289. }))
  290. osi.readDirRemainingNonRetriableErrors.Store(1)
  291. require.ErrorIs(t, st.ListBlobs(ctx, "", func(bm blob.Metadata) error {
  292. return nil
  293. }), errNonRetriable)
  294. osi.readDirRemainingFatalDirEntry.Store(1)
  295. require.ErrorIs(t, st.ListBlobs(ctx, "", func(bm blob.Metadata) error {
  296. return nil
  297. }), errNonRetriable)
  298. }
  299. func TestFileStorage_TouchBlob_ErrorHandling(t *testing.T) {
  300. t.Parallel()
  301. ctx := testlogging.Context(t)
  302. dataDir := testutil.TempDirectory(t)
  303. osi := newMockOS()
  304. st, err := New(ctx, &Options{
  305. Path: dataDir,
  306. Options: sharded.Options{
  307. DirectoryShards: []int{5, 2},
  308. },
  309. }, true)
  310. require.NoError(t, err)
  311. asFsImpl(t, st).osi = osi
  312. defer st.Close(ctx)
  313. require.NoError(t, st.PutBlob(ctx, "someblob1234567812345678", gather.FromSlice([]byte{1, 2, 3}), blob.PutOptions{}))
  314. osi.statRemainingErrors.Store(1)
  315. _, err = testutil.EnsureType[*fsStorage](t, st).TouchBlob(ctx, "someblob1234567812345678", 0)
  316. require.NoError(t, err)
  317. }
  318. func TestFileStorage_Misc(t *testing.T) {
  319. t.Parallel()
  320. ctx := testlogging.Context(t)
  321. dataDir := testutil.TempDirectory(t)
  322. st, err := New(ctx, &Options{
  323. Path: dataDir,
  324. Options: sharded.Options{
  325. DirectoryShards: []int{5, 2},
  326. },
  327. }, true)
  328. require.NoError(t, err)
  329. defer st.Close(ctx)
  330. require.NoError(t, st.FlushCaches(ctx)) // this does nothing
  331. require.Equal(t, st.DisplayName(), "Filesystem: "+dataDir)
  332. }
  333. func verifyBlobTimestampOrder(t *testing.T, st blob.Storage, want ...blob.ID) {
  334. t.Helper()
  335. blobs, err := blob.ListAllBlobs(testlogging.Context(t), st, "")
  336. if err != nil {
  337. t.Errorf("error listing blobs: %v", err)
  338. return
  339. }
  340. sort.Slice(blobs, func(i, j int) bool {
  341. return blobs[i].Timestamp.Before(blobs[j].Timestamp)
  342. })
  343. var got []blob.ID
  344. for _, b := range blobs {
  345. got = append(got, b.BlobID)
  346. }
  347. if !reflect.DeepEqual(got, want) {
  348. t.Errorf("incorrect blob order: %v, wanted %v", blobs, want)
  349. }
  350. }
  351. func assertNoError(t *testing.T, err error) {
  352. t.Helper()
  353. if err != nil {
  354. t.Errorf("err: %v", err)
  355. }
  356. }
  357. func newMockOS() *mockOS {
  358. return &mockOS{
  359. osInterface: realOS{},
  360. }
  361. }