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  }