github.com/pachyderm/pachyderm@v1.13.4/src/server/pkg/storage/track/tracker.go (about) 1 package track 2 3 import ( 4 "context" 5 "testing" 6 "time" 7 8 "github.com/jmoiron/sqlx" 9 "github.com/pachyderm/pachyderm/src/client/pkg/errors" 10 "github.com/pachyderm/pachyderm/src/client/pkg/require" 11 ) 12 13 var ( 14 // ErrObjectExists the object already exists 15 ErrObjectExists = errors.Errorf("object exists") 16 // ErrDanglingRef the operation would create a dangling reference 17 ErrDanglingRef = errors.Errorf("the operation would create a dangling reference") 18 // ErrTombstone cannot create object because it is marked as a tombstone 19 ErrTombstone = errors.Errorf("cannot create object because it is marked as a tombstone") 20 // ErrNotTombstone object cannot be deleted because it is not marked as a tombstone 21 ErrNotTombstone = errors.Errorf("object cannot be deleted because it is not marked as a tombstone") 22 // ErrSelfReference object cannot reference itself 23 ErrSelfReference = errors.Errorf("object cannot reference itself") 24 ) 25 26 // Tracker tracks objects and their references to one another. 27 type Tracker interface { 28 // CreateObject creates an object with id=id, and pointers to everything in pointsTo 29 // It errors with ErrObjectExists if the object already exists. Callers may be able to ignore this. 30 // It errors with ErrDanglingRef if any of the elements in pointsTo do not exist 31 // It errors with ErrTombstone if the object exists and is marked as a tombstone. Callers should retry until 32 // they successfully create the object. 33 // CreateObject should always be called *before* adding an object to an auxillary store. 34 CreateObject(ctx context.Context, id string, pointsTo []string, ttl time.Duration) error 35 36 // SetTTLPrefix sets the expiration time to current_time + ttl for all objects with ids starting with prefix 37 SetTTLPrefix(ctx context.Context, prefix string, ttl time.Duration) (time.Time, error) 38 39 // TODO: thoughts on these? 40 // SetTTL(ctx context.Context, id string, ttl time.Duration) error 41 // SetTTLBatch(ctx context.Context, ids []string, ttl time.Duration) error 42 43 // GetDownstream gets all objects immediately downstream of (pointed to by) object with id 44 GetDownstream(ctx context.Context, id string) ([]string, error) 45 46 // GetUpstream gets all objects immediately upstream of (pointing to) the object with id 47 GetUpstream(ctx context.Context, id string) ([]string, error) 48 49 // MarkTombstone causes the object to appear deleted. It cannot be created, and objects referencing it cannot 50 // be created until it is deleted. 51 // It errors if id has other objects referencing it. 52 // Marking something as a tombstone which is already a tombstone is not an error 53 MarkTombstone(ctx context.Context, id string) error 54 55 // FinishDelete deletes the object 56 // It is an error to call FinishDelete without calling MarkTombstone. 57 FinishDelete(ctx context.Context, id string) error 58 59 // IterateDeletable calls cb with all the objects objects which are no longer referenced and have expired or are tombstoned 60 IterateDeletable(ctx context.Context, cb func(id string) error) error 61 } 62 63 // TestTracker runs a TestSuite to ensure Tracker is properly implemented 64 func TestTracker(t *testing.T, newTracker func(testing.TB) Tracker) { 65 ctx := context.Background() 66 type test struct { 67 Name string 68 F func(*testing.T, Tracker) 69 } 70 tests := []test{ 71 { 72 "CreateSingleObject", 73 func(t *testing.T, tracker Tracker) { 74 require.Nil(t, tracker.CreateObject(ctx, "test-id", []string{}, 0)) 75 }, 76 }, 77 { 78 "CreateObjectDanglingRef", 79 func(t *testing.T, tracker Tracker) { 80 require.Equal(t, ErrDanglingRef, tracker.CreateObject(ctx, "test-id", []string{"none", "of", "these", "exist"}, 0)) 81 }, 82 }, 83 { 84 "ErrObjectExists", 85 func(t *testing.T, tracker Tracker) { 86 require.Nil(t, tracker.CreateObject(ctx, "test-id", []string{}, 0)) 87 require.Equal(t, ErrObjectExists, tracker.CreateObject(ctx, "test-id", []string{}, 0)) 88 }, 89 }, 90 { 91 "CreateMultipleObjects", 92 func(t *testing.T, tracker Tracker) { 93 require.Nil(t, tracker.CreateObject(ctx, "1", []string{}, 0)) 94 require.Nil(t, tracker.CreateObject(ctx, "2", []string{}, 0)) 95 require.Nil(t, tracker.CreateObject(ctx, "3", []string{"1", "2"}, 0)) 96 }, 97 }, 98 { 99 "GetReferences", 100 func(t *testing.T, tracker Tracker) { 101 require.Nil(t, tracker.CreateObject(ctx, "1", []string{}, 0)) 102 require.Nil(t, tracker.CreateObject(ctx, "2", []string{}, 0)) 103 require.Nil(t, tracker.CreateObject(ctx, "3", []string{"1", "2"}, 0)) 104 105 dwn, err := tracker.GetDownstream(ctx, "3") 106 require.Nil(t, err) 107 require.ElementsEqual(t, []string{"1", "2"}, dwn) 108 ups, err := tracker.GetUpstream(ctx, "2") 109 require.Nil(t, err) 110 require.ElementsEqual(t, []string{"3"}, ups) 111 }, 112 }, 113 { 114 "DeleteSingleObject", 115 func(t *testing.T, tracker Tracker) { 116 id := "test" 117 require.Nil(t, tracker.CreateObject(ctx, id, []string{}, 0)) 118 require.Nil(t, tracker.MarkTombstone(ctx, id)) 119 require.Nil(t, tracker.MarkTombstone(ctx, id)) // repeat mark tombstones should be allowed 120 require.Nil(t, tracker.FinishDelete(ctx, id)) 121 }, 122 }, 123 { 124 "ExpireSingleObject", 125 func(t *testing.T, tracker Tracker) { 126 require.Nil(t, tracker.CreateObject(ctx, "keep", []string{}, time.Hour)) 127 require.Nil(t, tracker.CreateObject(ctx, "expire", []string{}, time.Microsecond)) 128 time.Sleep(time.Millisecond) 129 130 var toExpire []string 131 tracker.IterateDeletable(ctx, func(id string) error { 132 toExpire = append(toExpire, id) 133 return nil 134 }) 135 require.ElementsEqual(t, []string{"expire"}, toExpire) 136 }, 137 }, 138 } 139 for _, test := range tests { 140 t.Run(test.Name, func(t *testing.T) { 141 tr := newTracker(t) 142 test.F(t, tr) 143 }) 144 } 145 } 146 147 // NewTestTracker returns a tracker scoped to the lifetime of the test 148 func NewTestTracker(t testing.TB, db *sqlx.DB) Tracker { 149 db.MustExec("CREATE SCHEMA storage") 150 db.MustExec(schema) 151 return NewPostgresTracker(db) 152 }