Skip to content

Commit eaa3bf3

Browse files
committed
datastore: gc enrichments
Signed-off-by: RTann <rtannenb@redhat.com> rh-pre-commit.version: 2.3.2 rh-pre-commit.check-secrets: ENABLED
1 parent 6624a82 commit eaa3bf3

4 files changed

Lines changed: 146 additions & 31 deletions

File tree

datastore/postgres/gc.go

Lines changed: 67 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ import (
1515
"github.com/prometheus/client_golang/prometheus/promauto"
1616
"github.com/quay/zlog"
1717
"golang.org/x/sync/semaphore"
18+
19+
"github.com/quay/claircore/libvuln/driver"
1820
)
1921

2022
var (
@@ -54,6 +56,8 @@ const (
5456
// If a full GC is required run this method until the returned int64 value
5557
// is 0.
5658
func (s *MatcherStore) GC(ctx context.Context, keep int) (int64, error) {
59+
ctx = zlog.ContextWithValues(ctx, "component", "datastore/postgres/GC")
60+
5761
// obtain update operations which need deletin'
5862
ops, totalOps, err := eligibleUpdateOpts(ctx, s.pool, keep)
5963
if err != nil {
@@ -81,20 +85,32 @@ func (s *MatcherStore) GC(ctx context.Context, keep int) (int64, error) {
8185
cpus := int64(runtime.GOMAXPROCS(0))
8286
sem := semaphore.NewWeighted(cpus)
8387

84-
errC := make(chan error, len(updaters))
88+
errC := make(chan error, len(updaters[driver.VulnerabilityKind])+len(updaters[driver.EnrichmentKind]))
8589

86-
for _, updater := range updaters {
87-
err = sem.Acquire(ctx, 1)
88-
if err != nil {
89-
break
90+
for kind, us := range updaters {
91+
var cleanup cleanupFunc
92+
switch kind {
93+
case driver.VulnerabilityKind:
94+
cleanup = vulnCleanup
95+
case driver.EnrichmentKind:
96+
cleanup = enrichmentCleanup
97+
default:
98+
zlog.Error(ctx).Str("kind", string(kind)).Msg("unknown updater kind; skipping cleanup")
99+
continue
90100
}
91-
go func(u string) {
92-
defer sem.Release(1)
93-
err := vulnCleanup(ctx, s.pool, u)
101+
for _, u := range us {
102+
err = sem.Acquire(ctx, 1)
94103
if err != nil {
95-
errC <- err
104+
break
96105
}
97-
}(updater)
106+
go func(cleanup cleanupFunc, u string) {
107+
defer sem.Release(1)
108+
err := cleanup(ctx, s.pool, u)
109+
if err != nil {
110+
errC <- err
111+
}
112+
}(cleanup, u)
113+
}
98114
}
99115

100116
// unconditionally wait for all in-flight go routines to return.
@@ -116,11 +132,11 @@ func (s *MatcherStore) GC(ctx context.Context, keep int) (int64, error) {
116132

117133
// distinctUpdaters returns all updaters which have registered an update
118134
// operation.
119-
func distinctUpdaters(ctx context.Context, pool *pgxpool.Pool) ([]string, error) {
135+
func distinctUpdaters(ctx context.Context, pool *pgxpool.Pool) (map[driver.UpdateKind][]string, error) {
120136
const (
121137
// will always contain at least two update operations
122138
selectUpdaters = `
123-
SELECT DISTINCT(updater) FROM update_operation;
139+
SELECT DISTINCT(updater), kind FROM update_operation;
124140
`
125141
)
126142
rows, err := pool.Query(ctx, selectUpdaters)
@@ -129,17 +145,20 @@ SELECT DISTINCT(updater) FROM update_operation;
129145
}
130146
defer rows.Close()
131147

132-
var updaters []string
148+
updaters := make(map[driver.UpdateKind][]string)
133149
for rows.Next() {
134-
var updater string
135-
err := rows.Scan(&updater)
150+
var (
151+
updater string
152+
kind driver.UpdateKind
153+
)
154+
err := rows.Scan(&updater, &kind)
136155
switch err {
137156
case nil:
138157
// hop out
139158
default:
140159
return nil, fmt.Errorf("error scanning updater: %v", err)
141160
}
142-
updaters = append(updaters, updater)
161+
updaters[kind] = append(updaters[kind], updater)
143162
}
144163
if rows.Err() != nil {
145164
return nil, rows.Err()
@@ -198,6 +217,8 @@ WHERE array_length(ordered_ops.refs, 1) > $2;
198217
return m, int64(len(m)), nil
199218
}
200219

220+
type cleanupFunc func(context.Context, *pgxpool.Pool, string) error
221+
201222
func vulnCleanup(ctx context.Context, pool *pgxpool.Pool, updater string) error {
202223
const (
203224
deleteOrphanedVulns = `
@@ -214,7 +235,7 @@ AND v1.id = v2.id;
214235
start := time.Now()
215236
ctx = zlog.ContextWithValues(ctx, "updater", updater)
216237
zlog.Debug(ctx).
217-
Msg("starting clean up")
238+
Msg("starting vuln clean up")
218239
res, err := pool.Exec(ctx, deleteOrphanedVulns, updater)
219240
if err != nil {
220241
gcCounter.WithLabelValues("deleteVulns", "false").Inc()
@@ -226,3 +247,32 @@ AND v1.id = v2.id;
226247

227248
return nil
228249
}
250+
251+
func enrichmentCleanup(ctx context.Context, pool *pgxpool.Pool, updater string) error {
252+
const (
253+
deleteOrphanedEnrichments = `
254+
DELETE FROM enrichment e1 USING
255+
enrichment e2
256+
LEFT JOIN uo_enrich uen
257+
ON e2.id = uen.enrich
258+
WHERE uen.enrich IS NULL
259+
AND e2.updater = $1
260+
AND e1.id = e2.id;
261+
`
262+
)
263+
264+
start := time.Now()
265+
ctx = zlog.ContextWithValues(ctx, "updater", updater)
266+
zlog.Debug(ctx).
267+
Msg("starting enrichment clean up")
268+
res, err := pool.Exec(ctx, deleteOrphanedEnrichments, updater)
269+
if err != nil {
270+
gcCounter.WithLabelValues("deleteEnrichments", "false").Inc()
271+
return fmt.Errorf("failed while exec'ing enrichment delete: %w", err)
272+
}
273+
zlog.Debug(ctx).Int64("rows affected", res.RowsAffected()).Msg("enrichments deleted")
274+
gcCounter.WithLabelValues("deleteEnrichments", "true").Inc()
275+
gcDuration.WithLabelValues("deleteEnrichments").Observe(time.Since(start).Seconds())
276+
277+
return nil
278+
}

datastore/postgres/gc_test.go

Lines changed: 72 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"crypto/rand"
66
"encoding/hex"
7+
"encoding/json"
78
"io"
89
"net/http"
910
"testing"
@@ -19,6 +20,8 @@ import (
1920
pgtest "github.com/quay/claircore/test/postgres"
2021
)
2122

23+
var _ driver.Updater = (*updaterMock)(nil)
24+
2225
type updaterMock struct {
2326
_name func() string
2427
_fetch func(_ context.Context, _ driver.Fingerprint) (io.ReadCloser, driver.Fingerprint, error)
@@ -37,6 +40,30 @@ func (u *updaterMock) Parse(ctx context.Context, contents io.ReadCloser) ([]*cla
3740
return u._parse(ctx, contents)
3841
}
3942

43+
var (
44+
_ driver.Updater = (*enricherMock)(nil)
45+
_ driver.EnrichmentUpdater = (*enricherMock)(nil)
46+
)
47+
48+
type enricherMock struct {
49+
driver.NoopUpdater
50+
_name func() string
51+
_fetch func(_ context.Context, _ driver.Fingerprint) (io.ReadCloser, driver.Fingerprint, error)
52+
_parse func(ctx context.Context, contents io.ReadCloser) ([]driver.EnrichmentRecord, error)
53+
}
54+
55+
func (e enricherMock) Name() string {
56+
return e._name()
57+
}
58+
59+
func (e enricherMock) FetchEnrichment(ctx context.Context, fingerprint driver.Fingerprint) (io.ReadCloser, driver.Fingerprint, error) {
60+
return e._fetch(ctx, fingerprint)
61+
}
62+
63+
func (e enricherMock) ParseEnrichment(ctx context.Context, contents io.ReadCloser) ([]driver.EnrichmentRecord, error) {
64+
return e._parse(ctx, contents)
65+
}
66+
4067
// TestGC confirms the garbage collection of
4168
// vulnerabilities works correctly.
4269
func TestGC(t *testing.T) {
@@ -60,6 +87,23 @@ func TestGC(t *testing.T) {
6087
},
6188
}
6289

90+
// mock returns exactly one random enrichment each time its Parse method is called.
91+
// each update operation will be associated with a single enrichment.
92+
mockEnrich := &enricherMock{
93+
_name: func() string { return "MockEnrichmentUpdater" },
94+
_fetch: func(_ context.Context, _ driver.Fingerprint) (io.ReadCloser, driver.Fingerprint, error) {
95+
return nil, "", nil
96+
},
97+
_parse: func(ctx context.Context, contents io.ReadCloser) ([]driver.EnrichmentRecord, error) {
98+
return []driver.EnrichmentRecord{
99+
{
100+
Tags: []string{randString(t)},
101+
Enrichment: json.RawMessage("{}"),
102+
},
103+
}, nil
104+
},
105+
}
106+
63107
// these tests maintain a one:one relationship between
64108
// update operations and a linked vulnerability for simplicty.
65109
// in other words, each update operation inserts one vuln and
@@ -116,14 +160,22 @@ func TestGC(t *testing.T) {
116160
locks,
117161
http.DefaultClient, // Used on purpose -- shouldn't actually get called by anything.
118162
updates.WithEnabled([]string{}),
163+
updates.WithFactories(map[string]driver.UpdaterSetFactory{
164+
"MockEnrichmentUpdater": func() driver.UpdaterSetFactory {
165+
set := driver.NewUpdaterSet()
166+
_ = set.Add(mockEnrich)
167+
return driver.StaticSet(set)
168+
}(),
169+
}),
119170
updates.WithOutOfTree([]driver.Updater{mock}),
120171
)
121172
if err != nil {
122173
t.Fatalf("failed creating update manager: %v", err)
123174
}
124175

176+
t.Logf("update Opts: %d", tt.updateOps)
125177
// run updater n times to create n update operations
126-
for i := 0; i < tt.updateOps; i++ {
178+
for range tt.updateOps {
127179
err := mgr.Run(ctx)
128180
if err != nil {
129181
t.Fatalf("manager failed to run: %v", err)
@@ -138,9 +190,16 @@ func TestGC(t *testing.T) {
138190
if len(ops["MockUpdater"]) != tt.updateOps {
139191
t.Fatalf("%s got: %v want: %v", tt.name, len(ops["MockUpdater"]), tt.updateOps)
140192
}
193+
ops, err = store.GetUpdateOperations(ctx, driver.EnrichmentKind)
194+
if err != nil {
195+
t.Fatalf("failed obtaining enrichment update ops: %v", err)
196+
}
197+
if len(ops["MockEnrichmentUpdater"]) != tt.updateOps {
198+
t.Fatalf("%s got: %v want: %v", tt.name, len(ops["MockEnrichmentUpdater"]), tt.updateOps)
199+
}
141200

142201
// run gc
143-
expectedNotDone := Max(tt.updateOps-tt.keep-GCThrottle, 0)
202+
expectedNotDone := max(2*(tt.updateOps-tt.keep)-GCThrottle, 0)
144203
notDone, err := store.GC(ctx, tt.keep)
145204
switch {
146205
case err != nil:
@@ -153,14 +212,20 @@ func TestGC(t *testing.T) {
153212
if tt.updateOps < tt.keep {
154213
wantKeep = tt.updateOps
155214
}
156-
ops, err = store.GetUpdateOperations(ctx, driver.VulnerabilityKind)
215+
expectedRemaining := 2*wantKeep + expectedNotDone
216+
217+
updaterOps, err := store.GetUpdateOperations(ctx, driver.VulnerabilityKind)
157218
if err != nil {
158219
t.Fatalf("failed obtaining update ops: %v", err)
159220
}
160-
t.Logf("ops %v", ops)
161-
expectedRemaining := wantKeep + expectedNotDone
162-
if len(ops["MockUpdater"]) != expectedRemaining {
163-
t.Fatalf("%s got: %v want: %v", tt.name, len(ops["MockUpdater"]), expectedRemaining)
221+
t.Logf("ops %v", updaterOps)
222+
enricherOps, err := store.GetUpdateOperations(ctx, driver.EnrichmentKind)
223+
if err != nil {
224+
t.Fatalf("failed obtaining enrichment update ops: %v", err)
225+
}
226+
t.Logf("ops %v", enricherOps)
227+
if len(updaterOps["MockUpdater"])+len(enricherOps["MockEnrichmentUpdater"]) != expectedRemaining {
228+
t.Fatalf("%s got: %v want: %v", tt.name, len(updaterOps["MockUpdater"])+len(enricherOps["MockEnrichmentUpdater"]), expectedRemaining)
164229
}
165230
})
166231
}
@@ -174,10 +239,3 @@ func randString(t *testing.T) string {
174239
}
175240
return hex.EncodeToString(buf)
176241
}
177-
178-
func Max(x, y int) int {
179-
if x < y {
180-
return y
181-
}
182-
return x
183-
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
CREATE INDEX IF NOT EXISTS uo_enrich_enrich_idx ON uo_enrich (enrich);
2+
CREATE INDEX IF NOT EXISTS uo_enrich_uo_idx ON uo_enrich (uo);
3+
CREATE INDEX IF NOT EXISTS enrichment_updater_idx ON enrichment (updater);

datastore/postgres/migrations/migrations.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,4 +120,8 @@ var MatcherMigrations = []migrate.Migration{
120120
ID: 14,
121121
Up: runFile("matcher/14-delete-rhcc-vulns.sql"),
122122
},
123+
{
124+
ID: 15,
125+
Up: runFile("matcher/15-enrichment-indexes.sql"),
126+
},
123127
}

0 commit comments

Comments
 (0)