91 lines
3.0 KiB
Go
91 lines
3.0 KiB
Go
package deployment
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
type inspectionCancelClient struct {
|
|
*inspectionClient
|
|
cancel context.CancelFunc
|
|
stage string
|
|
}
|
|
|
|
func (c *inspectionCancelClient) Exists(ctx context.Context, path string) (bool, error) {
|
|
if c.stage == "exists" || (c.stage == "lock-exists" && strings.HasSuffix(path, ".scert-lock")) {
|
|
c.cancel()
|
|
return false, errors.New("sensitive-read-error")
|
|
}
|
|
return c.memoryClient.Exists(ctx, path)
|
|
}
|
|
|
|
func (c *inspectionCancelClient) FileDigest(ctx context.Context, path string) (string, error) {
|
|
if c.stage == "digest" {
|
|
c.cancel()
|
|
return "", errors.New("sensitive-read-error")
|
|
}
|
|
return c.memoryClient.FileDigest(ctx, path)
|
|
}
|
|
|
|
func (c *inspectionCancelClient) ReadLockOwner(ctx context.Context, path string) (string, error) {
|
|
if c.stage == "owner" {
|
|
c.cancel()
|
|
return "", errors.New("sensitive-read-error")
|
|
}
|
|
return c.memoryClient.ReadLockOwner(ctx, path)
|
|
}
|
|
|
|
func TestRecoveryInspectionCancellationBoundaries(t *testing.T) {
|
|
for _, stage := range []string{"resolve", "exists", "digest", "lock-exists", "owner"} {
|
|
t.Run(stage, func(t *testing.T) {
|
|
server, memory, record := recoveryPreflightFixture()
|
|
ctx, cancel := context.WithCancel(t.Context())
|
|
defer cancel()
|
|
client := &inspectionCancelClient{inspectionClient: &inspectionClient{memoryClient: memory}, cancel: cancel, stage: stage}
|
|
journal := &memoryTransactionJournal{pending: []TransactionRecord{record}}
|
|
factory := &inspectionFactory{client: client}
|
|
service := NewServiceWithJournal(factory, journal)
|
|
result, err := service.InspectPending(ctx, record.ID, func(ctx context.Context, id string) (Server, error) {
|
|
deadline, ok := ctx.Deadline()
|
|
if !ok || time.Until(deadline) > 30*time.Second {
|
|
t.Fatal("inspection lacks bounded deadline")
|
|
}
|
|
if stage == "resolve" {
|
|
cancel()
|
|
return Server{}, errors.New("sensitive-resolve-error")
|
|
}
|
|
return server, nil
|
|
})
|
|
if !errors.Is(err, context.Canceled) || !reflect.DeepEqual(result, RecoveryInspection{}) {
|
|
t.Fatalf("cancellation lost: %+v %v", result, err)
|
|
}
|
|
if client.closed != factory.calls || client.mutations != 0 || len(journal.updated) != 0 || len(journal.finished) != 0 {
|
|
t.Fatal("cancellation changed state or leaked client")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRecoveryInspectionPreservesEarlierDeadline(t *testing.T) {
|
|
server, memory, record := recoveryPreflightFixture()
|
|
ctx, cancel := context.WithTimeout(t.Context(), time.Second)
|
|
defer cancel()
|
|
deadline, _ := ctx.Deadline()
|
|
client := &inspectionClient{memoryClient: memory}
|
|
service := NewServiceWithJournal(fixedClientFactory{client}, &memoryTransactionJournal{pending: []TransactionRecord{record}})
|
|
_, err := service.InspectPending(ctx, record.ID, func(ctx context.Context, id string) (Server, error) {
|
|
actual, ok := ctx.Deadline()
|
|
if !ok || !actual.Equal(deadline) {
|
|
t.Fatal("caller deadline extended")
|
|
}
|
|
return server, nil
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|