Skip to content

Commit f966445

Browse files
committed
blobfixture: add LastFailureAt flag
This commit adds the `LastFailureAt` flag to the fixture framework. When set, a fixture will not be GC'd for a week, even if it has been made obsolete by a more recent fixture. Informs: #160073 Release note: None
1 parent e3f3439 commit f966445

File tree

3 files changed

+105
-1
lines changed

3 files changed

+105
-1
lines changed

pkg/roachprod/blobfixture/metadata.go

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,11 @@ type FixtureMetadata struct {
4343

4444
// FingerprintTime is the aost used by the fingerprint command.
4545
FingerprintTime string `json:"fingerprint_time,omitempty"`
46+
47+
// LastFailureAt indicates that some test using this fixture has failed and we
48+
// want to prevent GC of this fixture for investigation purposes. If this
49+
// field is set, the fixture will not be GC'd until a week after this time.
50+
LastFailureAt *time.Time `json:"last_failure_at,omitempty"`
4651
}
4752

4853
func (f *FixtureMetadata) MarshalJson() ([]byte, error) {
@@ -113,6 +118,10 @@ func fixturesToGc(gcAt time.Time, allFixtures []FixtureMetadata) []fixtureToDele
113118
// made ready more than 24 hours ago.
114119
obsoleteThreshold := gcAt.Add(-24 * time.Hour)
115120

121+
// A fixture that has had a test failure within the past week is not eligible
122+
// for GC.
123+
failureGCThreshold := gcAt.Add(-7 * 24 * time.Hour)
124+
116125
toDelete := []fixtureToDelete{}
117126

118127
byKind := make(map[string][]FixtureMetadata)
@@ -144,10 +153,15 @@ func fixturesToGc(gcAt time.Time, allFixtures []FixtureMetadata) []fixtureToDele
144153
// NOTE: starting at 1 because index 0 is the most recent fixture and is
145154
// not eligible for garbage collection.
146155
for i := 1; i < len(fixtures); i++ {
156+
fixture := fixtures[i]
147157
successor := fixtures[i-1]
148158
if successor.ReadyAt.Before(obsoleteThreshold) {
159+
if fixture.LastFailureAt != nil && !fixture.LastFailureAt.Before(failureGCThreshold) {
160+
// Fixture has had a test failure within the past week, skip gc.
161+
continue
162+
}
149163
toDelete = append(toDelete, fixtureToDelete{
150-
metadata: fixtures[i],
164+
metadata: fixture,
151165
reason: fmt.Sprintf("fixture '%s' is was mode obsolete by '%s' at '%s'", fixtures[i].DataPath, successor.DataPath, successor.ReadyAt),
152166
})
153167
}

pkg/roachprod/blobfixture/registry.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,22 @@ func (r *Registry) GC(ctx context.Context, l *logger.Logger) error {
167167
return nil
168168
}
169169

170+
// MarkFailure marks the fixture at the given metadata path as having resulted
171+
// in a test failure. This prevents the fixture from being garbage collected,
172+
// providing time for investigation.
173+
func (r *Registry) MarkFailure(ctx context.Context, l *logger.Logger, metadataPath string) error {
174+
setTime := r.clock()
175+
err := r.updateMetadata(metadataPath, func(m *FixtureMetadata) error {
176+
m.LastFailureAt = &setTime
177+
return nil
178+
})
179+
if err != nil {
180+
return err
181+
}
182+
l.Printf("fixture '%s' marked last failure at '%s'", metadataPath, setTime)
183+
return nil
184+
}
185+
170186
func (r *Registry) Close() {
171187
_ = r.storage.Close()
172188
}
@@ -263,6 +279,45 @@ func (r *Registry) upsertMetadata(metadata FixtureMetadata) error {
263279
return writer.Close()
264280
}
265281

282+
func (r *Registry) updateMetadata(
283+
metadataPath string, update func(metadata *FixtureMetadata) error,
284+
) error {
285+
ctx := context.Background()
286+
json, err := r.maybeReadFile(ctx, metadataPath)
287+
if err != nil {
288+
return errors.Wrap(err, "failed to read metadata for update")
289+
}
290+
if json == nil {
291+
return errors.New("metadata does not exist for update")
292+
}
293+
294+
metadata := &FixtureMetadata{}
295+
if err := metadata.UnmarshalJson(json); err != nil {
296+
return errors.Wrap(err, "failed to unmarshal metadata for update")
297+
}
298+
299+
if err := update(metadata); err != nil {
300+
return errors.Wrap(err, "failed to update metadata")
301+
}
302+
303+
updatedJson, err := metadata.MarshalJson()
304+
if err != nil {
305+
return errors.Wrap(err, "failed to marshal updated metadata")
306+
}
307+
308+
writer, err := r.storage.Writer(ctx, metadata.MetadataPath)
309+
if err != nil {
310+
return errors.Wrap(err, "failed to create writer for updated metadata")
311+
}
312+
313+
if _, err := writer.Write(updatedJson); err != nil {
314+
_ = writer.Close()
315+
return errors.Wrap(err, "failed to write updated metadata")
316+
}
317+
318+
return writer.Close()
319+
}
320+
266321
func (r *Registry) deleteMetadata(metadata FixtureMetadata) error {
267322
return errors.Wrap(r.storage.Delete(context.Background(), metadata.MetadataPath), "failed to delete metadata")
268323
}

pkg/roachprod/blobfixture/registry_test.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ func TestFixtureRegistry(t *testing.T) {
5858
kind string
5959
createdAt time.Time
6060
readyAt time.Time
61+
lastFailureAt time.Time
6162
isLatestOfKind bool
6263
survivesGC bool
6364
}
@@ -127,6 +128,35 @@ func TestFixtureRegistry(t *testing.T) {
127128
survivesGC: true,
128129
isLatestOfKind: false,
129130
},
131+
{
132+
// This fixture was marked with a failure long ago, so it will be deleted
133+
// despite the flag.
134+
kind: "kind-marked-failure",
135+
createdAt: makeTime(-10),
136+
readyAt: makeTime(-9),
137+
lastFailureAt: makeTime(-8),
138+
survivesGC: false,
139+
isLatestOfKind: false,
140+
},
141+
{
142+
// This fixture was marked with a failure recently, so despite the fact
143+
// that it has been obsolete for more than a day, it will not be
144+
// deleted.
145+
kind: "kind-marked-failure",
146+
createdAt: makeTime(-6),
147+
readyAt: makeTime(-5),
148+
lastFailureAt: makeTime(-4),
149+
survivesGC: true,
150+
isLatestOfKind: false,
151+
},
152+
{
153+
// This is the most recent fixture of its kind, so it will not be deleted.
154+
kind: "kind-marked-failure",
155+
createdAt: makeTime(-1),
156+
readyAt: makeTime(-0.5),
157+
survivesGC: true,
158+
isLatestOfKind: true,
159+
},
130160
}
131161

132162
type fixturesCreated struct {
@@ -169,6 +199,11 @@ func TestFixtureRegistry(t *testing.T) {
169199
require.NoError(t, handle.SetReadyAt(ctx))
170200
}
171201

202+
if !f.lastFailureAt.IsZero() {
203+
now = f.lastFailureAt
204+
require.NoError(t, registry.MarkFailure(ctx, l, metadata.MetadataPath))
205+
}
206+
172207
created = append(created, fixturesCreated{
173208
fixture: f,
174209
metadata: metadata,

0 commit comments

Comments
 (0)