Skip to content

Commit 4df346d

Browse files
committed
internal/ctlog: ensure that immutable uploads are really so
We might want to take advantage of backends overwrite protection in the future.
1 parent 9aee27e commit 4df346d

File tree

3 files changed

+171
-87
lines changed

3 files changed

+171
-87
lines changed

internal/ctlog/ctlog.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -388,7 +388,7 @@ type UploadOptions struct {
388388

389389
var optsHashTile = &UploadOptions{Immutable: true}
390390
var optsDataTile = &UploadOptions{Compress: true, Immutable: true}
391-
var optsStaging = &UploadOptions{Compress: true, Immutable: true}
391+
var optsStaging = &UploadOptions{Compress: true}
392392
var optsIssuer = &UploadOptions{ContentType: "application/pkix-cert", Immutable: true}
393393
var optsCheckpoint = &UploadOptions{ContentType: "text/plain; charset=utf-8"}
394394

@@ -826,7 +826,7 @@ func (l *Log) sequencePool(ctx context.Context, p *pool) (err error) {
826826
// exercise the same code path as LoadLog.
827827
if err := applyStagedUploads(ctx, l.c, stagedUploads); err != nil {
828828
// This is also fatal, since we can't continue leaving behind missing
829-
// tiles. The next run of sequence will not upload them again, while
829+
// tiles. The next run of sequence would not upload them again, while
830830
// LoadLog will retry uploading them from the staging bundle.
831831
return fmtErrorf("%w: couldn't upload a tile: %w", errFatal, err)
832832
}

internal/ctlog/ctlog_test.go

+75-79
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,11 @@ import (
1010
"crypto/sha256"
1111
"crypto/x509"
1212
"encoding/base64"
13-
"errors"
1413
"flag"
1514
mathrand "math/rand"
1615
"reflect"
1716
"slices"
1817
"strconv"
19-
"strings"
2018
"sync/atomic"
2119
"testing"
2220
"time"
@@ -29,11 +27,9 @@ import (
2927

3028
var globalTime = time.Now().UnixMilli()
3129

32-
func init() {
33-
ctlog.SetTimeNowUnixMilli(func() int64 {
34-
return atomic.AddInt64(&globalTime, 1)
35-
})
36-
}
30+
func monotonicTime() int64 { return atomic.AddInt64(&globalTime, 1) }
31+
32+
func init() { ctlog.SetTimeNowUnixMilli(monotonicTime) }
3733

3834
var longFlag = flag.Bool("long", false, "run especially slow tests")
3935

@@ -428,24 +424,78 @@ func TestReloadWrongKey(t *testing.T) {
428424
}
429425
}
430426

431-
func TestFatalError(t *testing.T) {
427+
func TestStagingCollision(t *testing.T) {
432428
tl := NewEmptyTestLog(t)
433429
addCertificate(t, tl)
434-
addCertificate(t, tl)
435430
fatalIfErr(t, tl.Log.Sequence())
436431

437-
lockErr := errors.New("lock replace error")
438-
fail := func(old ctlog.LockedCheckpoint, new []byte) (bool, error) {
439-
return false, lockErr
432+
time := monotonicTime()
433+
ctlog.SetTimeNowUnixMilli(func() int64 { return time })
434+
t.Cleanup(func() { ctlog.SetTimeNowUnixMilli(monotonicTime) })
435+
436+
addCertificateExpectFailureWithSeed(t, tl, 'A')
437+
addCertificateExpectFailureWithSeed(t, tl, 'B')
438+
439+
tl.Config.Lock.(*MemoryLockBackend).ReplaceCallback = failLockAndNotPersist
440+
sequenceExpectFailure(t, tl)
441+
tl.CheckLog(1)
442+
443+
tl.Config.Lock.(*MemoryLockBackend).ReplaceCallback = nil
444+
tl = ReloadLog(t, tl)
445+
tl.CheckLog(1)
446+
447+
// First, cause the exact same staging bundle to be uploaded.
448+
449+
addCertificateWithSeed(t, tl, 'A')
450+
addCertificateWithSeed(t, tl, 'B')
451+
fatalIfErr(t, tl.Log.Sequence())
452+
tl.CheckLog(3)
453+
454+
// Again, but now due to a staging bundle upload error.
455+
456+
time++
457+
458+
addCertificateExpectFailureWithSeed(t, tl, 'C')
459+
addCertificateExpectFailureWithSeed(t, tl, 'D')
460+
461+
tl.Config.Backend.(*MemoryBackend).UploadCallback = failStagingButPersist
462+
fatalIfErr(t, tl.Log.Sequence())
463+
tl.CheckLog(3)
464+
465+
tl.Config.Backend.(*MemoryBackend).UploadCallback = nil
466+
467+
addCertificateWithSeed(t, tl, 'C')
468+
addCertificateWithSeed(t, tl, 'D')
469+
fatalIfErr(t, tl.Log.Sequence())
470+
tl.CheckLog(5)
471+
472+
// We wanted to also test reaching the same point through two different
473+
// sequencing paths, as suggested in
474+
// https://github.com/FiloSottile/sunlight/pull/18#discussion_r1704174301
475+
// but that doesn't seem possible since time is required to move forward at
476+
// each sequencing.
477+
}
478+
479+
func sequenceExpectFailure(t *testing.T, tl *TestLog) {
480+
t.Helper()
481+
if err := tl.Log.Sequence(); err == nil {
482+
t.Error("expected error, got nil")
440483
}
484+
}
441485

442-
tl.Config.Lock.(*MemoryLockBackend).ReplaceCallback = fail
486+
func TestFatalError(t *testing.T) {
487+
tl := NewEmptyTestLog(t)
488+
addCertificate(t, tl)
489+
addCertificate(t, tl)
490+
fatalIfErr(t, tl.Log.Sequence())
491+
492+
tl.Config.Lock.(*MemoryLockBackend).ReplaceCallback = failLockAndNotPersist
443493
addCertificateExpectFailure(t, tl)
444494

445495
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
446496
defer cancel()
447-
if err := tl.Log.RunSequencer(ctx, 1*time.Millisecond); !errors.Is(err, lockErr) {
448-
t.Errorf("expected fatal error, got %v", err)
497+
if err := tl.Log.RunSequencer(ctx, 1*time.Millisecond); err == nil {
498+
t.Errorf("expected fatal error, got nil")
449499
}
450500
tl.CheckLog(2)
451501

@@ -474,10 +524,7 @@ func TestSequenceErrors(t *testing.T) {
474524
// retried, and the same tiles are generated and uploaded again.
475525
name: "LockUpload",
476526
breakSeq: func(tl *TestLog) {
477-
fail := func(old ctlog.LockedCheckpoint, new []byte) (bool, error) {
478-
return false, errors.New("lock replace error")
479-
}
480-
tl.Config.Lock.(*MemoryLockBackend).ReplaceCallback = fail
527+
tl.Config.Lock.(*MemoryLockBackend).ReplaceCallback = failLockAndNotPersist
481528
},
482529
unbreakSeq: func(tl *TestLog) {
483530
tl.Config.Lock.(*MemoryLockBackend).ReplaceCallback = nil
@@ -490,10 +537,7 @@ func TestSequenceErrors(t *testing.T) {
490537
// persisted anyway, such as a response timeout.
491538
name: "LockUploadPersisted",
492539
breakSeq: func(tl *TestLog) {
493-
fail := func(old ctlog.LockedCheckpoint, new []byte) (bool, error) {
494-
return true, errors.New("lock response timeout")
495-
}
496-
tl.Config.Lock.(*MemoryLockBackend).ReplaceCallback = fail
540+
tl.Config.Lock.(*MemoryLockBackend).ReplaceCallback = failLockButPersist
497541
},
498542
unbreakSeq: func(tl *TestLog) {
499543
tl.Config.Lock.(*MemoryLockBackend).ReplaceCallback = nil
@@ -504,13 +548,7 @@ func TestSequenceErrors(t *testing.T) {
504548
{
505549
name: "CheckpointUpload",
506550
breakSeq: func(tl *TestLog) {
507-
fail := func(key string, data []byte) (apply bool, err error) {
508-
if key == "checkpoint" {
509-
return false, errors.New("checkpoint upload error")
510-
}
511-
return true, nil
512-
}
513-
tl.Config.Backend.(*MemoryBackend).UploadCallback = fail
551+
tl.Config.Backend.(*MemoryBackend).UploadCallback = failCheckpointAndNotPersist
514552
},
515553
unbreakSeq: func(tl *TestLog) {
516554
tl.Config.Backend.(*MemoryBackend).UploadCallback = nil
@@ -521,13 +559,7 @@ func TestSequenceErrors(t *testing.T) {
521559
{
522560
name: "CheckpointUploadPersisted",
523561
breakSeq: func(tl *TestLog) {
524-
fail := func(key string, data []byte) (apply bool, err error) {
525-
if key == "checkpoint" {
526-
return true, errors.New("checkpoint upload error")
527-
}
528-
return true, nil
529-
}
530-
tl.Config.Backend.(*MemoryBackend).UploadCallback = fail
562+
tl.Config.Backend.(*MemoryBackend).UploadCallback = failCheckpointButPersist
531563
},
532564
unbreakSeq: func(tl *TestLog) {
533565
tl.Config.Backend.(*MemoryBackend).UploadCallback = nil
@@ -538,13 +570,7 @@ func TestSequenceErrors(t *testing.T) {
538570
{
539571
name: "StagingUpload",
540572
breakSeq: func(tl *TestLog) {
541-
fail := func(key string, data []byte) (apply bool, err error) {
542-
if strings.HasPrefix(key, "staging/") {
543-
return false, errors.New("staging upload error")
544-
}
545-
return true, nil
546-
}
547-
tl.Config.Backend.(*MemoryBackend).UploadCallback = fail
573+
tl.Config.Backend.(*MemoryBackend).UploadCallback = failStagingAndNotPersist
548574
},
549575
unbreakSeq: func(tl *TestLog) {
550576
tl.Config.Backend.(*MemoryBackend).UploadCallback = nil
@@ -555,13 +581,7 @@ func TestSequenceErrors(t *testing.T) {
555581
{
556582
name: "StagingUploadPersisted",
557583
breakSeq: func(tl *TestLog) {
558-
fail := func(key string, data []byte) (apply bool, err error) {
559-
if strings.HasPrefix(key, "staging/") {
560-
return true, errors.New("staging upload error")
561-
}
562-
return true, nil
563-
}
564-
tl.Config.Backend.(*MemoryBackend).UploadCallback = fail
584+
tl.Config.Backend.(*MemoryBackend).UploadCallback = failStagingButPersist
565585
},
566586
unbreakSeq: func(tl *TestLog) {
567587
tl.Config.Backend.(*MemoryBackend).UploadCallback = nil
@@ -572,13 +592,7 @@ func TestSequenceErrors(t *testing.T) {
572592
{
573593
name: "DataTileUpload",
574594
breakSeq: func(tl *TestLog) {
575-
fail := func(key string, data []byte) (apply bool, err error) {
576-
if strings.HasPrefix(key, "tile/data/") {
577-
return false, errors.New("tile upload error")
578-
}
579-
return true, nil
580-
}
581-
tl.Config.Backend.(*MemoryBackend).UploadCallback = fail
595+
tl.Config.Backend.(*MemoryBackend).UploadCallback = failDataTileAndNotPersist
582596
},
583597
unbreakSeq: func(tl *TestLog) {
584598
tl.Config.Backend.(*MemoryBackend).UploadCallback = nil
@@ -589,13 +603,7 @@ func TestSequenceErrors(t *testing.T) {
589603
{
590604
name: "DataTileUploadPersisted",
591605
breakSeq: func(tl *TestLog) {
592-
fail := func(key string, data []byte) (apply bool, err error) {
593-
if strings.HasPrefix(key, "tile/data/") {
594-
return true, errors.New("tile upload error")
595-
}
596-
return true, nil
597-
}
598-
tl.Config.Backend.(*MemoryBackend).UploadCallback = fail
606+
tl.Config.Backend.(*MemoryBackend).UploadCallback = failDataTileButPersist
599607
},
600608
unbreakSeq: func(tl *TestLog) {
601609
tl.Config.Backend.(*MemoryBackend).UploadCallback = nil
@@ -606,13 +614,7 @@ func TestSequenceErrors(t *testing.T) {
606614
{
607615
name: "Level0TileUpload",
608616
breakSeq: func(tl *TestLog) {
609-
fail := func(key string, data []byte) (apply bool, err error) {
610-
if strings.HasPrefix(key, "tile/0/") {
611-
return false, errors.New("tile upload error")
612-
}
613-
return true, nil
614-
}
615-
tl.Config.Backend.(*MemoryBackend).UploadCallback = fail
617+
tl.Config.Backend.(*MemoryBackend).UploadCallback = failTile0AndNotPersist
616618
},
617619
unbreakSeq: func(tl *TestLog) {
618620
tl.Config.Backend.(*MemoryBackend).UploadCallback = nil
@@ -623,13 +625,7 @@ func TestSequenceErrors(t *testing.T) {
623625
{
624626
name: "Level0TileUploadPersisted",
625627
breakSeq: func(tl *TestLog) {
626-
fail := func(key string, data []byte) (apply bool, err error) {
627-
if strings.HasPrefix(key, "tile/0/") {
628-
return true, errors.New("tile upload error")
629-
}
630-
return true, nil
631-
}
632-
tl.Config.Backend.(*MemoryBackend).UploadCallback = fail
628+
tl.Config.Backend.(*MemoryBackend).UploadCallback = failTile0ButPersist
633629
},
634630
unbreakSeq: func(tl *TestLog) {
635631
tl.Config.Backend.(*MemoryBackend).UploadCallback = nil

0 commit comments

Comments
 (0)