11package decomposedfs_test
22
33import (
4+ "os"
5+
46 provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
57 . "github.com/onsi/ginkgo/v2"
68 . "github.com/onsi/gomega"
@@ -68,7 +70,7 @@ var _ = Describe("RollbackUpload", func() {
6870 Expect (revsBefore ).To (HaveLen (1 ))
6971
7072 Expect (env .Fs .MarkProcessing (env .Ctx , ref , true , "session-overwrite" )).To (Succeed ())
71- Expect (env .Fs .RollbackUpload (env .Ctx , ref , "session-overwrite" , true , result .SizeDiff )).To (Succeed ())
73+ Expect (env .Fs .RollbackUpload (env .Ctx , ref , "session-overwrite" , storage. RollbackInfo { NodeExisted : true , SizeDiff : result .SizeDiff } )).To (Succeed ())
7274
7375 revsAfter , err := env .Fs .ListRevisions (env .Ctx , ref )
7476 Expect (err ).ToNot (HaveOccurred ())
@@ -104,7 +106,7 @@ var _ = Describe("RollbackUpload", func() {
104106 Expect (err ).ToNot (HaveOccurred ())
105107
106108 Expect (env .Fs .MarkProcessing (env .Ctx , ref , true , "session-b" )).To (Succeed ())
107- Expect (env .Fs .RollbackUpload (env .Ctx , ref , "session-a" , true , 10 )).To (Succeed ())
109+ Expect (env .Fs .RollbackUpload (env .Ctx , ref , "session-a" , storage. RollbackInfo { NodeExisted : true , SizeDiff : 10 } )).To (Succeed ())
108110
109111 n , err := env .Lookup .NodeFromResource (env .Ctx , ref )
110112 Expect (err ).ToNot (HaveOccurred ())
@@ -120,7 +122,117 @@ var _ = Describe("RollbackUpload", func() {
120122 ResourceId : env .SpaceRootRes ,
121123 Path : "/dir1/does-not-exist.txt" ,
122124 }
123- Expect (env .Fs .RollbackUpload (env .Ctx , missingRef , "any-session-id" , false , 0 )).To (Succeed ())
125+ Expect (env .Fs .RollbackUpload (env .Ctx , missingRef , "any-session-id" , storage.RollbackInfo {})).To (Succeed ())
126+ })
127+ })
128+
129+ // A node whose metadata is gone but whose file remains, e.g. because an
130+ // ancestor was trashed while the upload was in flight. ReadNode fails on the
131+ // missing parent id, so the rollback has only the session to go on.
132+ Context ("orphaned node: the metadata is unreadable" , func () {
133+ var (
134+ idRef * provider.Reference
135+ nodePath string
136+ dir1Ref * provider.Reference
137+ info storage.RollbackInfo
138+ )
139+
140+ // parentSize is the size dir1 accounts for, which the propagation moves.
141+ parentSize := func () uint64 {
142+ parentInfo , err := env .Fs .GetMD (env .Ctx , dir1Ref , []string {}, []string {})
143+ Expect (err ).ToNot (HaveOccurred ())
144+ return parentInfo .Size
145+ }
146+
147+ // orphan prepares an upload, then purges the target's metadata. It returns
148+ // the parent size from before the optimistic propagation.
149+ orphan := func () uint64 {
150+ dir1Ref = & provider.Reference {ResourceId : env .SpaceRootRes , Path : "/dir1" }
151+ sizeBefore := parentSize ()
152+
153+ _ , err := env .Fs .TouchFile (env .Ctx , ref , false , "" )
154+ Expect (err ).ToNot (HaveOccurred ())
155+
156+ n , err := env .Lookup .NodeFromResource (env .Ctx , ref )
157+ Expect (err ).ToNot (HaveOccurred ())
158+ nodePath = n .InternalPath ()
159+ // Address the node by id: the path walk needs the parent metadata the
160+ // purge below destroys.
161+ idRef = & provider.Reference {ResourceId : & provider.ResourceId {
162+ StorageId : env .SpaceRootRes .GetStorageId (),
163+ SpaceId : env .SpaceRootRes .GetSpaceId (),
164+ OpaqueId : n .ID ,
165+ }}
166+
167+ result , err := env .Fs .PrepareUpload (env .Ctx , ref , "session-orphan" , storage.UploadInfo {
168+ NodeExisted : false ,
169+ Size : 30 ,
170+ })
171+ Expect (err ).ToNot (HaveOccurred ())
172+ Expect (env .Fs .MarkProcessing (env .Ctx , ref , true , "session-orphan" )).To (Succeed ())
173+ Expect (parentSize ()).To (Equal (sizeBefore + 30 ), "the optimistic size should be propagated" )
174+
175+ // Purge, not remove: the cached attributes have to go too.
176+ Expect (env .Lookup .MetadataBackend ().Purge (env .Ctx , nodePath )).To (Succeed ())
177+ _ , err = env .Lookup .NodeFromResource (env .Ctx , idRef )
178+ Expect (err ).To (HaveOccurred (), "the node should be unreadable" )
179+
180+ info = storage.RollbackInfo {
181+ SizeDiff : result .SizeDiff ,
182+ NodeID : idRef .GetResourceId ().GetOpaqueId (),
183+ ParentID : n .ParentID ,
184+ Filename : "rollback-target.txt" ,
185+ Size : 30 ,
186+ }
187+ return sizeBefore
188+ }
189+
190+ It ("releases the quota and removes the unreachable node" , func () {
191+ sizeBefore := orphan ()
192+
193+ Expect (env .Fs .RollbackUpload (env .Ctx , idRef , "session-orphan" , info )).To (Succeed ())
194+
195+ Expect (parentSize ()).To (Equal (sizeBefore ))
196+
197+ _ , err := os .Stat (nodePath )
198+ Expect (err ).To (HaveOccurred (), "the orphaned node should be gone" )
199+ })
200+
201+ // Without them there is no way to reach the parent, so report the failure
202+ // rather than pretending the upload was rolled back.
203+ It ("reports the lookup failure when the session carries no ids" , func () {
204+ orphan ()
205+
206+ err := env .Fs .RollbackUpload (env .Ctx , idRef , "session-orphan" , storage.RollbackInfo {SizeDiff : 30 })
207+
208+ Expect (err ).To (MatchError (ContainSubstring ("node lookup failed" )))
209+ _ , statErr := os .Stat (nodePath )
210+ Expect (statErr ).ToNot (HaveOccurred (), "the node should be left for a retry" )
211+ })
212+
213+ // The space is what the propagation walks up to, so there is nothing to
214+ // walk without it.
215+ It ("reports the lookup failure when the reference names no space" , func () {
216+ orphan ()
217+
218+ err := env .Fs .RollbackUpload (env .Ctx , & provider.Reference {Path : "/dir1/rollback-target.txt" }, "session-orphan" , info )
219+
220+ Expect (err ).To (MatchError (ContainSubstring ("node lookup failed" )))
221+ _ , statErr := os .Stat (nodePath )
222+ Expect (statErr ).ToNot (HaveOccurred (), "the node should be left for a retry" )
223+ })
224+
225+ // Removing the node first would destroy the parent id the retry needs.
226+ It ("keeps the node when the quota cannot be released" , func () {
227+ orphan ()
228+ // A parent id pointing nowhere fails the walk up the tree.
229+ info .ParentID = "does-not-exist"
230+
231+ err := env .Fs .RollbackUpload (env .Ctx , idRef , "session-orphan" , info )
232+
233+ Expect (err ).To (MatchError (ContainSubstring ("could not revert propagate" )))
234+ _ , statErr := os .Stat (nodePath )
235+ Expect (statErr ).ToNot (HaveOccurred (), "the node should be left for a retry" )
124236 })
125237 })
126238})
0 commit comments