| index 98d4ba761..c4b1e21ef 100644 |
| --- a/internal/console/apiruns_key_test.go |
| +++ b/internal/console/apiruns_key_test.go |
| @@ -699,6 +699,51 @@ func TestRunKeyedLostAdmissionSettledByItsKey(t *testing.T) { |
| } |
| } |
| |
| +// keysDroppedStore commits the admission's record write, then has a record |
| +// writer drop run_keys (a box rolled back to code that does not know them) |
| +// while the record still holds the run, and answers err. |
| +type keysDroppedStore struct { |
| + release.Store |
| + recordKey string |
| + err error |
| + once sync.Once |
| + t *testing.T |
| +} |
| + |
| +func (m *keysDroppedStore) Put(ctx context.Context, key string, body []byte, ifMatch string) (string, error) { |
| + etag, err := m.Store.Put(ctx, key, body, ifMatch) |
| + if err != nil || key != m.recordKey { |
| + return etag, err |
| + } |
| + m.once.Do(func() { |
| + drop := func(r *provision.Record) error { r.RunKeys = nil; return nil } |
| + if _, e := provision.SetRecordRun(context.Background(), m.Store, release.OpsAccount, "shop", runTestHost, drop); e != nil { |
| + m.t.Errorf("keysDroppedStore: %v", e) |
| + } |
| + }) |
| + return etag, m.err |
| +} |
| + |
| +// A keyed admission whose answer was lost, and whose key is gone from the |
| +// record while the record still names the run, is admitted: the objects stay |
| +// and the answer names the run, as the unkeyed settle would have. |
| +func TestRunKeyedSettleKeepsARunTheRecordHoldsWhenItsKeyIsGone(t *testing.T) { |
| + fx := keyedFixture(t) |
| + fx.srv.cfg.Releases = &keysDroppedStore{Store: fx.rel, recordKey: provision.RecordKey(release.OpsAccount, "shop", runTestHost), |
| + err: errors.New("connection reset by peer"), t: t} |
| + c := fx.createKeyed(t, fx.member, testKey, fileMeta("a,b,c"), xcsv("a,b,c")) |
| + rec := fx.instRecord(t) |
| + if rec.ActiveRun == nil || rec.ActiveRun.ID != c.ID || len(rec.RunKeys) != 0 { |
| + t.Fatalf("answer %+v; record run %+v keys %+v, want the run held with no key", c, rec.ActiveRun, rec.RunKeys) |
| + } |
| + if c.State != provision.RunQueued { |
| + t.Errorf("answered %+v, want the record's queued state", c) |
| + } |
| + if keys, _ := fx.rel.List(context.Background(), runs.Prefix(release.OpsAccount, "shop", c.ID)); len(keys) != 2 { |
| + t.Fatalf("the held run's objects are %q, want request.json and in/x.csv", keys) |
| + } |
| +} |
| + |
| // twinOnRetryStore loses both of updateRecord's attempts: the first record |
| // write is refused as a conflict without landing, and before the second a |
| // twin's keyed admission lands, so the second conflicts for real. |