github.com/quay/claircore@v1.5.28/datastore/postgres/gc_test.go (about)

     1  package postgres
     2  
     3  import (
     4  	"context"
     5  	"crypto/rand"
     6  	"encoding/hex"
     7  	"io"
     8  	"net/http"
     9  	"testing"
    10  
    11  	"github.com/quay/zlog"
    12  
    13  	"github.com/quay/claircore"
    14  	"github.com/quay/claircore/libvuln/driver"
    15  	"github.com/quay/claircore/libvuln/updates"
    16  	"github.com/quay/claircore/pkg/ctxlock"
    17  	"github.com/quay/claircore/test"
    18  	"github.com/quay/claircore/test/integration"
    19  	pgtest "github.com/quay/claircore/test/postgres"
    20  )
    21  
    22  type updaterMock struct {
    23  	_name  func() string
    24  	_fetch func(_ context.Context, _ driver.Fingerprint) (io.ReadCloser, driver.Fingerprint, error)
    25  	_parse func(ctx context.Context, contents io.ReadCloser) ([]*claircore.Vulnerability, error)
    26  }
    27  
    28  func (u *updaterMock) Name() string {
    29  	return u._name()
    30  }
    31  
    32  func (u *updaterMock) Fetch(ctx context.Context, fp driver.Fingerprint) (io.ReadCloser, driver.Fingerprint, error) {
    33  	return u._fetch(ctx, fp)
    34  }
    35  
    36  func (u *updaterMock) Parse(ctx context.Context, contents io.ReadCloser) ([]*claircore.Vulnerability, error) {
    37  	return u._parse(ctx, contents)
    38  }
    39  
    40  // TestGC confirms the garbage collection of
    41  // vulnerabilities works correctly.
    42  func TestGC(t *testing.T) {
    43  	integration.NeedDB(t)
    44  
    45  	// mock returns exactly one random vuln each time its Parse method is called.
    46  	// each update operation will be associated with a single vuln.
    47  	mock := &updaterMock{
    48  		_name: func() string { return "MockUpdater" },
    49  		_fetch: func(_ context.Context, _ driver.Fingerprint) (io.ReadCloser, driver.Fingerprint, error) {
    50  			return nil, "", nil
    51  		},
    52  		_parse: func(ctx context.Context, contents io.ReadCloser) ([]*claircore.Vulnerability, error) {
    53  			return []*claircore.Vulnerability{
    54  				{
    55  					Name:    randString(t),
    56  					Updater: "MockUpdater",
    57  					Package: test.GenUniquePackages(1)[0],
    58  				},
    59  			}, nil
    60  		},
    61  	}
    62  
    63  	// these tests maintain a one:one relationship between
    64  	// update operations and a linked vulnerability for simplicty.
    65  	// in other words, each update operation inserts one vuln and
    66  	// each deletion of an update operation should induce a deletion
    67  	// of one vuln.
    68  	table := []struct {
    69  		// name of test case
    70  		name string
    71  		// number of update operations to create
    72  		updateOps int
    73  		// number of update operations to keep
    74  		keep int
    75  	}{
    76  		{
    77  			"Small",
    78  			4,
    79  			3,
    80  		},
    81  		{
    82  			"Large",
    83  			100,
    84  			50,
    85  		},
    86  		{
    87  			"Odd",
    88  			37,
    89  			23,
    90  		},
    91  		{
    92  			"Inversed",
    93  			10,
    94  			50,
    95  		},
    96  		{
    97  			"Throttle",
    98  			60,
    99  			5,
   100  		},
   101  	}
   102  
   103  	for _, tt := range table {
   104  		t.Run(tt.name, func(t *testing.T) {
   105  			ctx := zlog.Test(context.Background(), t)
   106  			pool := pgtest.TestMatcherDB(ctx, t)
   107  			store := NewMatcherStore(pool)
   108  			locks, err := ctxlock.New(ctx, pool)
   109  			if err != nil {
   110  				t.Error(err)
   111  			}
   112  			defer locks.Close(ctx)
   113  			mgr, err := updates.NewManager(
   114  				ctx,
   115  				NewMatcherStore(pool),
   116  				locks,
   117  				http.DefaultClient, // Used on purpose -- shouldn't actually get called by anything.
   118  				updates.WithEnabled([]string{}),
   119  				updates.WithOutOfTree([]driver.Updater{mock}),
   120  			)
   121  			if err != nil {
   122  				t.Fatalf("failed creating update manager: %v", err)
   123  			}
   124  
   125  			// run updater n times to create n update operations
   126  			for i := 0; i < tt.updateOps; i++ {
   127  				err := mgr.Run(ctx)
   128  				if err != nil {
   129  					t.Fatalf("manager failed to run: %v", err)
   130  				}
   131  			}
   132  
   133  			// confirm update operations exist
   134  			ops, err := store.GetUpdateOperations(ctx, driver.VulnerabilityKind)
   135  			if err != nil {
   136  				t.Fatalf("failed obtaining update ops: %v", err)
   137  			}
   138  			if len(ops["MockUpdater"]) != tt.updateOps {
   139  				t.Fatalf("%s got: %v want: %v", tt.name, len(ops["MockUpdater"]), tt.updateOps)
   140  			}
   141  
   142  			// run gc
   143  			expectedNotDone := Max(tt.updateOps-tt.keep-GCThrottle, 0)
   144  			notDone, err := store.GC(ctx, tt.keep)
   145  			switch {
   146  			case err != nil:
   147  				t.Fatalf("error while performing GC: %v", err)
   148  			case notDone != int64(expectedNotDone):
   149  				t.Fatalf("%s got: %v, want: %v", tt.name, notDone, expectedNotDone)
   150  			}
   151  
   152  			wantKeep := tt.keep
   153  			if tt.updateOps < tt.keep {
   154  				wantKeep = tt.updateOps
   155  			}
   156  			ops, err = store.GetUpdateOperations(ctx, driver.VulnerabilityKind)
   157  			if err != nil {
   158  				t.Fatalf("failed obtaining update ops: %v", err)
   159  			}
   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)
   164  			}
   165  		})
   166  	}
   167  }
   168  
   169  func randString(t *testing.T) string {
   170  	buf := make([]byte, 4, 4)
   171  	_, err := io.ReadAtLeast(rand.Reader, buf, len(buf))
   172  	if err != nil {
   173  		t.Fatalf("failed to generate random string: %v", err)
   174  	}
   175  	return hex.EncodeToString(buf)
   176  }
   177  
   178  func Max(x, y int) int {
   179  	if x < y {
   180  		return y
   181  	}
   182  	return x
   183  }