github.com/pachyderm/pachyderm@v1.13.4/src/server/pkg/collection/collection_test.go (about)

     1  package collection
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"fmt"
     7  	"os"
     8  	"strconv"
     9  	"strings"
    10  	"sync"
    11  	"testing"
    12  	"time"
    13  
    14  	"github.com/pachyderm/pachyderm/src/client"
    15  	"github.com/pachyderm/pachyderm/src/client/pfs"
    16  	"github.com/pachyderm/pachyderm/src/client/pkg/errors"
    17  	"github.com/pachyderm/pachyderm/src/client/pkg/require"
    18  	"github.com/pachyderm/pachyderm/src/client/pps"
    19  	"github.com/pachyderm/pachyderm/src/server/pkg/testetcd"
    20  	"github.com/pachyderm/pachyderm/src/server/pkg/uuid"
    21  	"github.com/pachyderm/pachyderm/src/server/pkg/watch"
    22  
    23  	etcd "github.com/coreos/etcd/clientv3"
    24  	"github.com/gogo/protobuf/types"
    25  )
    26  
    27  var (
    28  	pipelineIndex *Index = &Index{
    29  		Field: "Pipeline",
    30  		Multi: false,
    31  	}
    32  	commitMultiIndex *Index = &Index{
    33  		Field: "Provenance",
    34  		Multi: true,
    35  	}
    36  )
    37  
    38  func TestDryrun(t *testing.T) {
    39  	etcdClient := getEtcdClient()
    40  	uuidPrefix := uuid.NewWithoutDashes()
    41  
    42  	jobInfos := NewCollection(etcdClient, uuidPrefix, nil, &pps.JobInfo{}, nil, nil)
    43  
    44  	job := &pps.JobInfo{
    45  		Job:      client.NewJob("j1"),
    46  		Pipeline: client.NewPipeline("p1"),
    47  	}
    48  	err := NewDryrunSTM(context.Background(), etcdClient, func(stm STM) error {
    49  		jobInfos := jobInfos.ReadWrite(stm)
    50  		jobInfos.Put(job.Job.ID, job)
    51  		return nil
    52  	})
    53  	require.NoError(t, err)
    54  
    55  	jobInfosReadonly := jobInfos.ReadOnly(context.Background())
    56  	err = jobInfosReadonly.Get("j1", job)
    57  	require.True(t, IsErrNotFound(err))
    58  }
    59  
    60  func TestDelNonexistant(t *testing.T) {
    61  	require.NoError(t, testetcd.WithEnv(func(e *testetcd.Env) error {
    62  		c := e.EtcdClient
    63  		uuidPrefix := uuid.NewWithoutDashes()
    64  
    65  		jobInfos := NewCollection(c, uuidPrefix, nil, &pps.JobInfo{}, nil, nil)
    66  
    67  		_, err := NewSTM(context.Background(), c, func(stm STM) error {
    68  			err := jobInfos.ReadWrite(stm).Delete("test")
    69  			require.True(t, IsErrNotFound(err))
    70  			return err
    71  		})
    72  		require.True(t, IsErrNotFound(err))
    73  		return nil
    74  	}))
    75  }
    76  
    77  func TestGetAfterDel(t *testing.T) {
    78  	etcdClient := getEtcdClient()
    79  	uuidPrefix := uuid.NewWithoutDashes()
    80  
    81  	jobInfos := NewCollection(etcdClient, uuidPrefix, nil, &pps.JobInfo{}, nil, nil)
    82  
    83  	j1 := &pps.JobInfo{
    84  		Job:      client.NewJob("j1"),
    85  		Pipeline: client.NewPipeline("p1"),
    86  	}
    87  	j2 := &pps.JobInfo{
    88  		Job:      client.NewJob("j2"),
    89  		Pipeline: client.NewPipeline("p1"),
    90  	}
    91  	j3 := &pps.JobInfo{
    92  		Job:      client.NewJob("j3"),
    93  		Pipeline: client.NewPipeline("p2"),
    94  	}
    95  	_, err := NewSTM(context.Background(), etcdClient, func(stm STM) error {
    96  		jobInfos := jobInfos.ReadWrite(stm)
    97  		jobInfos.Put(j1.Job.ID, j1)
    98  		jobInfos.Put(j2.Job.ID, j2)
    99  		jobInfos.Put(j3.Job.ID, j3)
   100  		return nil
   101  	})
   102  	require.NoError(t, err)
   103  
   104  	_, err = NewSTM(context.Background(), etcdClient, func(stm STM) error {
   105  		job := &pps.JobInfo{}
   106  		jobInfos := jobInfos.ReadWrite(stm)
   107  		if err := jobInfos.Get(j1.Job.ID, job); err != nil {
   108  			return err
   109  		}
   110  
   111  		if err := jobInfos.Get("j4", job); !IsErrNotFound(err) {
   112  			return errors.Wrapf(err, "Expected ErrNotFound for key '%s', but got", "j4")
   113  		}
   114  
   115  		jobInfos.DeleteAll()
   116  
   117  		if err := jobInfos.Get(j1.Job.ID, job); !IsErrNotFound(err) {
   118  			return errors.Wrapf(err, "Expected ErrNotFound for key '%s', but got", j1.Job.ID)
   119  		}
   120  		if err := jobInfos.Get(j2.Job.ID, job); !IsErrNotFound(err) {
   121  			return errors.Wrapf(err, "Expected ErrNotFound for key '%s', but got", j2.Job.ID)
   122  		}
   123  		return nil
   124  	})
   125  	require.NoError(t, err)
   126  
   127  	count, err := jobInfos.ReadOnly(context.Background()).Count()
   128  	require.NoError(t, err)
   129  	require.Equal(t, int64(0), count)
   130  }
   131  
   132  func TestDeletePrefix(t *testing.T) {
   133  	etcdClient := getEtcdClient()
   134  	uuidPrefix := uuid.NewWithoutDashes()
   135  
   136  	jobInfos := NewCollection(etcdClient, uuidPrefix, nil, &pps.JobInfo{}, nil, nil)
   137  
   138  	j1 := &pps.JobInfo{
   139  		Job:      client.NewJob("prefix/suffix/job"),
   140  		Pipeline: client.NewPipeline("p"),
   141  	}
   142  	j2 := &pps.JobInfo{
   143  		Job:      client.NewJob("prefix/suffix/job2"),
   144  		Pipeline: client.NewPipeline("p"),
   145  	}
   146  	j3 := &pps.JobInfo{
   147  		Job:      client.NewJob("prefix/job3"),
   148  		Pipeline: client.NewPipeline("p"),
   149  	}
   150  	j4 := &pps.JobInfo{
   151  		Job:      client.NewJob("job4"),
   152  		Pipeline: client.NewPipeline("p"),
   153  	}
   154  
   155  	_, err := NewSTM(context.Background(), etcdClient, func(stm STM) error {
   156  		jobInfos := jobInfos.ReadWrite(stm)
   157  		jobInfos.Put(j1.Job.ID, j1)
   158  		jobInfos.Put(j2.Job.ID, j2)
   159  		jobInfos.Put(j3.Job.ID, j3)
   160  		jobInfos.Put(j4.Job.ID, j4)
   161  		return nil
   162  	})
   163  	require.NoError(t, err)
   164  
   165  	_, err = NewSTM(context.Background(), etcdClient, func(stm STM) error {
   166  		job := &pps.JobInfo{}
   167  		jobInfos := jobInfos.ReadWrite(stm)
   168  
   169  		jobInfos.DeleteAllPrefix("prefix/suffix")
   170  		if err := jobInfos.Get(j1.Job.ID, job); !IsErrNotFound(err) {
   171  			return errors.Wrapf(err, "Expected ErrNotFound for key '%s', but got", j1.Job.ID)
   172  		}
   173  		if err := jobInfos.Get(j2.Job.ID, job); !IsErrNotFound(err) {
   174  			return errors.Wrapf(err, "Expected ErrNotFound for key '%s', but got", j2.Job.ID)
   175  		}
   176  		if err := jobInfos.Get(j3.Job.ID, job); err != nil {
   177  			return err
   178  		}
   179  		if err := jobInfos.Get(j4.Job.ID, job); err != nil {
   180  			return err
   181  		}
   182  
   183  		jobInfos.DeleteAllPrefix("prefix")
   184  		if err := jobInfos.Get(j1.Job.ID, job); !IsErrNotFound(err) {
   185  			return errors.Wrapf(err, "Expected ErrNotFound for key '%s', but got", j1.Job.ID)
   186  		}
   187  		if err := jobInfos.Get(j2.Job.ID, job); !IsErrNotFound(err) {
   188  			return errors.Wrapf(err, "Expected ErrNotFound for key '%s', but got", j2.Job.ID)
   189  		}
   190  		if err := jobInfos.Get(j3.Job.ID, job); !IsErrNotFound(err) {
   191  			return errors.Wrapf(err, "Expected ErrNotFound for key '%s', but got", j3.Job.ID)
   192  		}
   193  		if err := jobInfos.Get(j4.Job.ID, job); err != nil {
   194  			return err
   195  		}
   196  
   197  		jobInfos.Put(j1.Job.ID, j1)
   198  		if err := jobInfos.Get(j1.Job.ID, job); err != nil {
   199  			return err
   200  		}
   201  
   202  		jobInfos.DeleteAllPrefix("prefix/suffix")
   203  		if err := jobInfos.Get(j1.Job.ID, job); !IsErrNotFound(err) {
   204  			return errors.Wrapf(err, "Expected ErrNotFound for key '%s', but got", j1.Job.ID)
   205  		}
   206  
   207  		jobInfos.Put(j2.Job.ID, j2)
   208  		if err := jobInfos.Get(j2.Job.ID, job); err != nil {
   209  			return err
   210  		}
   211  
   212  		return nil
   213  	})
   214  	require.NoError(t, err)
   215  
   216  	job := &pps.JobInfo{}
   217  	jobs := jobInfos.ReadOnly(context.Background())
   218  	require.True(t, IsErrNotFound(jobs.Get(j1.Job.ID, job)))
   219  	require.NoError(t, jobs.Get(j2.Job.ID, job))
   220  	require.Equal(t, j2, job)
   221  	require.True(t, IsErrNotFound(jobs.Get(j3.Job.ID, job)))
   222  	require.NoError(t, jobs.Get(j4.Job.ID, job))
   223  	require.Equal(t, j4, job)
   224  }
   225  
   226  func TestIndex(t *testing.T) {
   227  	etcdClient := getEtcdClient()
   228  	uuidPrefix := uuid.NewWithoutDashes()
   229  
   230  	jobInfos := NewCollection(etcdClient, uuidPrefix, []*Index{pipelineIndex}, &pps.JobInfo{}, nil, nil)
   231  
   232  	j1 := &pps.JobInfo{
   233  		Job:      client.NewJob("j1"),
   234  		Pipeline: client.NewPipeline("p1"),
   235  	}
   236  	j2 := &pps.JobInfo{
   237  		Job:      client.NewJob("j2"),
   238  		Pipeline: client.NewPipeline("p1"),
   239  	}
   240  	j3 := &pps.JobInfo{
   241  		Job:      client.NewJob("j3"),
   242  		Pipeline: client.NewPipeline("p2"),
   243  	}
   244  	_, err := NewSTM(context.Background(), etcdClient, func(stm STM) error {
   245  		jobInfos := jobInfos.ReadWrite(stm)
   246  		jobInfos.Put(j1.Job.ID, j1)
   247  		jobInfos.Put(j2.Job.ID, j2)
   248  		jobInfos.Put(j3.Job.ID, j3)
   249  		return nil
   250  	})
   251  	require.NoError(t, err)
   252  
   253  	jobInfosReadonly := jobInfos.ReadOnly(context.Background())
   254  
   255  	job := &pps.JobInfo{}
   256  	i := 1
   257  	require.NoError(t, jobInfosReadonly.GetByIndex(pipelineIndex, j1.Pipeline, job, DefaultOptions, func(ID string) error {
   258  		switch i {
   259  		case 1:
   260  			require.Equal(t, j1.Job.ID, ID)
   261  			require.Equal(t, j1, job)
   262  		case 2:
   263  			require.Equal(t, j2.Job.ID, ID)
   264  			require.Equal(t, j2, job)
   265  		case 3:
   266  			t.Fatal("too many jobs")
   267  		}
   268  		i++
   269  		return nil
   270  	}))
   271  
   272  	i = 1
   273  	require.NoError(t, jobInfosReadonly.GetByIndex(pipelineIndex, j3.Pipeline, job, DefaultOptions, func(ID string) error {
   274  		switch i {
   275  		case 1:
   276  			require.Equal(t, j3.Job.ID, ID)
   277  			require.Equal(t, j3, job)
   278  		case 2:
   279  			t.Fatal("too many jobs")
   280  		}
   281  		i++
   282  		return nil
   283  	}))
   284  }
   285  
   286  func TestIndexWatch(t *testing.T) {
   287  	etcdClient := getEtcdClient()
   288  	uuidPrefix := uuid.NewWithoutDashes()
   289  
   290  	jobInfos := NewCollection(etcdClient, uuidPrefix, []*Index{pipelineIndex}, &pps.JobInfo{}, nil, nil)
   291  
   292  	j1 := &pps.JobInfo{
   293  		Job:      client.NewJob("j1"),
   294  		Pipeline: client.NewPipeline("p1"),
   295  	}
   296  	_, err := NewSTM(context.Background(), etcdClient, func(stm STM) error {
   297  		jobInfos := jobInfos.ReadWrite(stm)
   298  		jobInfos.Put(j1.Job.ID, j1)
   299  		return nil
   300  	})
   301  	require.NoError(t, err)
   302  
   303  	jobInfosReadonly := jobInfos.ReadOnly(context.Background())
   304  
   305  	watcher, err := jobInfosReadonly.WatchByIndex(pipelineIndex, j1.Pipeline)
   306  	eventCh := watcher.Watch()
   307  	require.NoError(t, err)
   308  	var ID string
   309  	job := new(pps.JobInfo)
   310  	event := <-eventCh
   311  	require.NoError(t, event.Err)
   312  	require.Equal(t, event.Type, watch.EventPut)
   313  	require.NoError(t, event.Unmarshal(&ID, job))
   314  	require.Equal(t, j1.Job.ID, ID)
   315  	require.Equal(t, j1, job)
   316  
   317  	// Now we will put j1 again, unchanged.  We want to make sure
   318  	// that we do not receive an event.
   319  	_, err = NewSTM(context.Background(), etcdClient, func(stm STM) error {
   320  		jobInfos := jobInfos.ReadWrite(stm)
   321  		jobInfos.Put(j1.Job.ID, j1)
   322  		return nil
   323  	})
   324  	require.NoError(t, err)
   325  
   326  	select {
   327  	case event := <-eventCh:
   328  		t.Fatalf("should not have received an event %v", event)
   329  	case <-time.After(2 * time.Second):
   330  	}
   331  
   332  	j2 := &pps.JobInfo{
   333  		Job:      client.NewJob("j2"),
   334  		Pipeline: client.NewPipeline("p1"),
   335  	}
   336  
   337  	_, err = NewSTM(context.Background(), etcdClient, func(stm STM) error {
   338  		jobInfos := jobInfos.ReadWrite(stm)
   339  		jobInfos.Put(j2.Job.ID, j2)
   340  		return nil
   341  	})
   342  	require.NoError(t, err)
   343  
   344  	event = <-eventCh
   345  	require.NoError(t, event.Err)
   346  	require.Equal(t, event.Type, watch.EventPut)
   347  	require.NoError(t, event.Unmarshal(&ID, job))
   348  	require.Equal(t, j2.Job.ID, ID)
   349  	require.Equal(t, j2, job)
   350  
   351  	j1Prime := &pps.JobInfo{
   352  		Job:      client.NewJob("j1"),
   353  		Pipeline: client.NewPipeline("p3"),
   354  	}
   355  	_, err = NewSTM(context.Background(), etcdClient, func(stm STM) error {
   356  		jobInfos := jobInfos.ReadWrite(stm)
   357  		jobInfos.Put(j1.Job.ID, j1Prime)
   358  		return nil
   359  	})
   360  	require.NoError(t, err)
   361  
   362  	event = <-eventCh
   363  	require.NoError(t, event.Err)
   364  	require.Equal(t, event.Type, watch.EventDelete)
   365  	require.NoError(t, event.Unmarshal(&ID, job))
   366  	require.Equal(t, j1.Job.ID, ID)
   367  
   368  	_, err = NewSTM(context.Background(), etcdClient, func(stm STM) error {
   369  		jobInfos := jobInfos.ReadWrite(stm)
   370  		jobInfos.Delete(j2.Job.ID)
   371  		return nil
   372  	})
   373  	require.NoError(t, err)
   374  
   375  	event = <-eventCh
   376  	require.NoError(t, event.Err)
   377  	require.Equal(t, event.Type, watch.EventDelete)
   378  	require.NoError(t, event.Unmarshal(&ID, job))
   379  	require.Equal(t, j2.Job.ID, ID)
   380  }
   381  
   382  func TestMultiIndex(t *testing.T) {
   383  	etcdClient := getEtcdClient()
   384  	uuidPrefix := uuid.NewWithoutDashes()
   385  
   386  	cis := NewCollection(etcdClient, uuidPrefix, []*Index{commitMultiIndex}, &pfs.CommitInfo{}, nil, nil)
   387  
   388  	c1 := &pfs.CommitInfo{
   389  		Commit: client.NewCommit("repo", "c1"),
   390  		Provenance: []*pfs.CommitProvenance{
   391  			client.NewCommitProvenance("in", "master", "c1"),
   392  			client.NewCommitProvenance("in", "master", "c2"),
   393  			client.NewCommitProvenance("in", "master", "c3"),
   394  		},
   395  	}
   396  	c2 := &pfs.CommitInfo{
   397  		Commit: client.NewCommit("repo", "c2"),
   398  		Provenance: []*pfs.CommitProvenance{
   399  			client.NewCommitProvenance("in", "master", "c1"),
   400  			client.NewCommitProvenance("in", "master", "c2"),
   401  			client.NewCommitProvenance("in", "master", "c3"),
   402  		},
   403  	}
   404  	_, err := NewSTM(context.Background(), etcdClient, func(stm STM) error {
   405  		cis := cis.ReadWrite(stm)
   406  		cis.Put(c1.Commit.ID, c1)
   407  		cis.Put(c2.Commit.ID, c2)
   408  		return nil
   409  	})
   410  	require.NoError(t, err)
   411  
   412  	cisReadonly := cis.ReadOnly(context.Background())
   413  
   414  	// Test that the first key retrieves both r1 and r2
   415  	ci := &pfs.CommitInfo{}
   416  	i := 1
   417  	require.NoError(t, cisReadonly.GetByIndex(commitMultiIndex, client.NewCommit("in", "c1"), ci, DefaultOptions, func(ID string) error {
   418  		if i == 1 {
   419  			require.Equal(t, c1.Commit.ID, ID)
   420  			require.Equal(t, c1, ci)
   421  		} else {
   422  			require.Equal(t, c2.Commit.ID, ID)
   423  			require.Equal(t, c2, ci)
   424  		}
   425  		i++
   426  		return nil
   427  	}))
   428  
   429  	// Test that the second key retrieves both r1 and r2
   430  	i = 1
   431  	require.NoError(t, cisReadonly.GetByIndex(commitMultiIndex, client.NewCommit("in", "c2"), ci, DefaultOptions, func(ID string) error {
   432  		if i == 1 {
   433  			require.Equal(t, c1.Commit.ID, ID)
   434  			require.Equal(t, c1, ci)
   435  		} else {
   436  			require.Equal(t, c2.Commit.ID, ID)
   437  			require.Equal(t, c2, ci)
   438  		}
   439  		i++
   440  		return nil
   441  	}))
   442  
   443  	// replace "c3" in the provenance of c1 with "c4"
   444  	c1.Provenance[2].Commit.ID = "c4"
   445  	_, err = NewSTM(context.Background(), etcdClient, func(stm STM) error {
   446  		cis := cis.ReadWrite(stm)
   447  		cis.Put(c1.Commit.ID, c1)
   448  		return nil
   449  	})
   450  	require.NoError(t, err)
   451  
   452  	// Now "c3" only retrieves c2 (indexes are updated)
   453  	require.NoError(t, cisReadonly.GetByIndex(commitMultiIndex, client.NewCommit("in", "c3"), ci, DefaultOptions, func(ID string) error {
   454  		require.Equal(t, c2.Commit.ID, ID)
   455  		require.Equal(t, c2, ci)
   456  		return nil
   457  	}))
   458  
   459  	// And "C4" only retrieves r1 (indexes are updated)
   460  	require.NoError(t, cisReadonly.GetByIndex(commitMultiIndex, client.NewCommit("in", "c4"), ci, DefaultOptions, func(ID string) error {
   461  		require.Equal(t, c1.Commit.ID, ID)
   462  		require.Equal(t, c1, ci)
   463  		return nil
   464  	}))
   465  
   466  	// Delete c1 from etcd completely
   467  	_, err = NewSTM(context.Background(), etcdClient, func(stm STM) error {
   468  		cis := cis.ReadWrite(stm)
   469  		cis.Delete(c1.Commit.ID)
   470  		return nil
   471  	})
   472  	require.NoError(t, err)
   473  
   474  	// Now "c1" only retrieves c2
   475  	require.NoError(t, cisReadonly.GetByIndex(commitMultiIndex, client.NewCommit("in", "c1"), ci, DefaultOptions, func(ID string) error {
   476  		require.Equal(t, c2.Commit.ID, ID)
   477  		require.Equal(t, c2, ci)
   478  		return nil
   479  	}))
   480  }
   481  
   482  func TestBoolIndex(t *testing.T) {
   483  	etcdClient := getEtcdClient()
   484  	uuidPrefix := uuid.NewWithoutDashes()
   485  	boolValues := NewCollection(etcdClient, uuidPrefix, []*Index{{
   486  		Field: "Value",
   487  		Multi: false,
   488  	}}, &types.BoolValue{}, nil, nil)
   489  
   490  	r1 := &types.BoolValue{
   491  		Value: true,
   492  	}
   493  	r2 := &types.BoolValue{
   494  		Value: false,
   495  	}
   496  	_, err := NewSTM(context.Background(), etcdClient, func(stm STM) error {
   497  		boolValues := boolValues.ReadWrite(stm)
   498  		boolValues.Put("true", r1)
   499  		boolValues.Put("false", r2)
   500  		return nil
   501  	})
   502  	require.NoError(t, err)
   503  
   504  	// Test that we don't format the index string incorrectly
   505  	resp, err := etcdClient.Get(context.Background(), uuidPrefix, etcd.WithPrefix())
   506  	require.NoError(t, err)
   507  	for _, kv := range resp.Kvs {
   508  		if !bytes.Contains(kv.Key, []byte("__index_")) {
   509  			continue // not an index record
   510  		}
   511  		require.True(t,
   512  			bytes.Contains(kv.Key, []byte("__index_Value/true")) ||
   513  				bytes.Contains(kv.Key, []byte("__index_Value/false")), string(kv.Key))
   514  	}
   515  }
   516  
   517  var epsilon = &types.BoolValue{Value: true}
   518  
   519  func TestTTL(t *testing.T) {
   520  	etcdClient := getEtcdClient()
   521  	uuidPrefix := uuid.NewWithoutDashes()
   522  
   523  	clxn := NewCollection(etcdClient, uuidPrefix, nil, &types.BoolValue{}, nil, nil)
   524  	const TTL = 5
   525  	_, err := NewSTM(context.Background(), etcdClient, func(stm STM) error {
   526  		return clxn.ReadWrite(stm).PutTTL("key", epsilon, TTL)
   527  	})
   528  	require.NoError(t, err)
   529  
   530  	var actualTTL int64
   531  	_, err = NewSTM(context.Background(), etcdClient, func(stm STM) error {
   532  		var err error
   533  		actualTTL, err = clxn.ReadWrite(stm).TTL("key")
   534  		return err
   535  	})
   536  	require.NoError(t, err)
   537  	require.True(t, actualTTL > 0 && actualTTL < TTL, "actualTTL was %v", actualTTL)
   538  }
   539  
   540  func TestTTLExpire(t *testing.T) {
   541  	etcdClient := getEtcdClient()
   542  	uuidPrefix := uuid.NewWithoutDashes()
   543  
   544  	clxn := NewCollection(etcdClient, uuidPrefix, nil, &types.BoolValue{}, nil, nil)
   545  	const TTL = 5
   546  	_, err := NewSTM(context.Background(), etcdClient, func(stm STM) error {
   547  		return clxn.ReadWrite(stm).PutTTL("key", epsilon, TTL)
   548  	})
   549  	require.NoError(t, err)
   550  
   551  	time.Sleep((TTL + 1) * time.Second)
   552  	value := &types.BoolValue{}
   553  	err = clxn.ReadOnly(context.Background()).Get("key", value)
   554  	require.NotNil(t, err)
   555  	require.Matches(t, "not found", err.Error())
   556  }
   557  
   558  func TestTTLExtend(t *testing.T) {
   559  	etcdClient := getEtcdClient()
   560  	uuidPrefix := uuid.NewWithoutDashes()
   561  
   562  	// Put value with short TLL & check that it was set
   563  	clxn := NewCollection(etcdClient, uuidPrefix, nil, &types.BoolValue{}, nil, nil)
   564  	const TTL = 5
   565  	_, err := NewSTM(context.Background(), etcdClient, func(stm STM) error {
   566  		return clxn.ReadWrite(stm).PutTTL("key", epsilon, TTL)
   567  	})
   568  	require.NoError(t, err)
   569  
   570  	var actualTTL int64
   571  	_, err = NewSTM(context.Background(), etcdClient, func(stm STM) error {
   572  		var err error
   573  		actualTTL, err = clxn.ReadWrite(stm).TTL("key")
   574  		return err
   575  	})
   576  	require.NoError(t, err)
   577  	require.True(t, actualTTL > 0 && actualTTL < TTL, "actualTTL was %v", actualTTL)
   578  
   579  	// Put value with new, longer TLL and check that it was set
   580  	const LongerTTL = 15
   581  	_, err = NewSTM(context.Background(), etcdClient, func(stm STM) error {
   582  		return clxn.ReadWrite(stm).PutTTL("key", epsilon, LongerTTL)
   583  	})
   584  	require.NoError(t, err)
   585  
   586  	_, err = NewSTM(context.Background(), etcdClient, func(stm STM) error {
   587  		var err error
   588  		actualTTL, err = clxn.ReadWrite(stm).TTL("key")
   589  		return err
   590  	})
   591  	require.NoError(t, err)
   592  	require.True(t, actualTTL > TTL && actualTTL < LongerTTL, "actualTTL was %v", actualTTL)
   593  }
   594  
   595  func TestIteration(t *testing.T) {
   596  	etcdClient := getEtcdClient()
   597  	t.Run("one-val-per-txn", func(t *testing.T) {
   598  		uuidPrefix := uuid.NewWithoutDashes()
   599  		col := NewCollection(etcdClient, uuidPrefix, nil, &types.Empty{}, nil, nil)
   600  		numVals := 1000
   601  		for i := 0; i < numVals; i++ {
   602  			_, err := NewSTM(context.Background(), etcdClient, func(stm STM) error {
   603  				return col.ReadWrite(stm).Put(fmt.Sprintf("%d", i), &types.Empty{})
   604  			})
   605  			require.NoError(t, err)
   606  		}
   607  		ro := col.ReadOnly(context.Background())
   608  		val := &types.Empty{}
   609  		i := numVals - 1
   610  		require.NoError(t, ro.List(val, DefaultOptions, func(key string) error {
   611  			require.Equal(t, fmt.Sprintf("%d", i), key)
   612  			i--
   613  			return nil
   614  		}))
   615  	})
   616  	t.Run("many-vals-per-txn", func(t *testing.T) {
   617  		uuidPrefix := uuid.NewWithoutDashes()
   618  		col := NewCollection(etcdClient, uuidPrefix, nil, &types.Empty{}, nil, nil)
   619  		numBatches := 10
   620  		valsPerBatch := 7
   621  		for i := 0; i < numBatches; i++ {
   622  			_, err := NewSTM(context.Background(), etcdClient, func(stm STM) error {
   623  				for j := 0; j < valsPerBatch; j++ {
   624  					if err := col.ReadWrite(stm).Put(fmt.Sprintf("%d", i*valsPerBatch+j), &types.Empty{}); err != nil {
   625  						return err
   626  					}
   627  				}
   628  				return nil
   629  			})
   630  			require.NoError(t, err)
   631  		}
   632  		vals := make(map[string]bool)
   633  		ro := col.ReadOnly(context.Background())
   634  		val := &types.Empty{}
   635  		require.NoError(t, ro.List(val, DefaultOptions, func(key string) error {
   636  			require.False(t, vals[key], "saw value %s twice", key)
   637  			vals[key] = true
   638  			return nil
   639  		}))
   640  		require.Equal(t, numBatches*valsPerBatch, len(vals), "didn't receive every value")
   641  	})
   642  	t.Run("large-vals", func(t *testing.T) {
   643  		uuidPrefix := uuid.NewWithoutDashes()
   644  		col := NewCollection(etcdClient, uuidPrefix, nil, &pfs.Repo{}, nil, nil)
   645  		numVals := 100
   646  		longString := strings.Repeat("foo\n", 1024*256) // 1 MB worth of foo
   647  		for i := 0; i < numVals; i++ {
   648  			_, err := NewSTM(context.Background(), etcdClient, func(stm STM) error {
   649  				if err := col.ReadWrite(stm).Put(fmt.Sprintf("%d", i), &pfs.Repo{Name: longString}); err != nil {
   650  					return err
   651  				}
   652  				return nil
   653  			})
   654  			require.NoError(t, err)
   655  		}
   656  		ro := col.ReadOnly(context.Background())
   657  		val := &pfs.Repo{}
   658  		vals := make(map[string]bool)
   659  		valsOrder := []string{}
   660  		require.NoError(t, ro.List(val, DefaultOptions, func(key string) error {
   661  			require.False(t, vals[key], "saw value %s twice", key)
   662  			vals[key] = true
   663  			valsOrder = append(valsOrder, key)
   664  			return nil
   665  		}))
   666  		for i, key := range valsOrder {
   667  			require.Equal(t, key, strconv.Itoa(numVals-i-1), "incorrect order returned")
   668  		}
   669  		require.Equal(t, numVals, len(vals), "didn't receive every value")
   670  		vals = make(map[string]bool)
   671  		valsOrder = []string{}
   672  		require.NoError(t, ro.List(val, &Options{etcd.SortByCreateRevision, etcd.SortAscend, true}, func(key string) error {
   673  			require.False(t, vals[key], "saw value %s twice", key)
   674  			vals[key] = true
   675  			valsOrder = append(valsOrder, key)
   676  			return nil
   677  		}))
   678  		for i, key := range valsOrder {
   679  			require.Equal(t, key, strconv.Itoa(i), "incorrect order returned")
   680  		}
   681  		require.Equal(t, numVals, len(vals), "didn't receive every value")
   682  	})
   683  }
   684  
   685  var etcdClient *etcd.Client
   686  var etcdClientOnce sync.Once
   687  
   688  func getEtcdClient() *etcd.Client {
   689  	etcdClientOnce.Do(func() {
   690  		var err error
   691  		host := os.Getenv("VM_IP")
   692  		if host == "" {
   693  			host = "localhost"
   694  		}
   695  		etcdClient, err = etcd.New(etcd.Config{
   696  			Endpoints:   []string{fmt.Sprintf("%v:32379", host)},
   697  			DialOptions: client.DefaultDialOptions(),
   698  		})
   699  		if err != nil {
   700  			panic(err)
   701  		}
   702  	})
   703  	return etcdClient
   704  }