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 }