Add collectible resources to metadata gc
Adds a registration function to metadata which allows plugins to register resources to be garbage collected. These resources allow defining resources types which are ephemeral and stored outside the metadata plugin without extending it. The garbage collection of these resources will not fail the metadata gc process if their removal fails. These resources may be referenced by existing metadata store resources but may not be used to reference metadata store resources for the purpose of preventing garbage collection. Signed-off-by: Derek McGowan <derek@mcg.dev>
This commit is contained in:
@@ -157,7 +157,7 @@ func TestGCRoots(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
checkNodeC(ctx, t, db, expected, func(ctx context.Context, tx *bolt.Tx, nc chan<- gc.Node) error {
|
||||
return scanRoots(ctx, tx, nc)
|
||||
return startGCContext(ctx, nil).scanRoots(ctx, tx, nc)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -230,9 +230,10 @@ func TestGCRemove(t *testing.T) {
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
c := startGCContext(ctx, nil)
|
||||
|
||||
checkNodes(ctx, t, db, all, func(ctx context.Context, tx *bolt.Tx, fn func(context.Context, gc.Node) error) error {
|
||||
return scanAll(ctx, tx, fn)
|
||||
return c.scanAll(ctx, tx, fn)
|
||||
})
|
||||
if t.Failed() {
|
||||
t.Fatal("Scan all failed")
|
||||
@@ -240,7 +241,7 @@ func TestGCRemove(t *testing.T) {
|
||||
|
||||
if err := db.Update(func(tx *bolt.Tx) error {
|
||||
for _, n := range deleted {
|
||||
if err := remove(ctx, tx, n); err != nil {
|
||||
if err := c.remove(ctx, tx, n); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -250,7 +251,7 @@ func TestGCRemove(t *testing.T) {
|
||||
}
|
||||
|
||||
checkNodes(ctx, t, db, remaining, func(ctx context.Context, tx *bolt.Tx, fn func(context.Context, gc.Node) error) error {
|
||||
return scanAll(ctx, tx, fn)
|
||||
return c.scanAll(ctx, tx, fn)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -370,10 +371,11 @@ func TestGCRefs(t *testing.T) {
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
c := startGCContext(ctx, nil)
|
||||
|
||||
for n, nodes := range refs {
|
||||
checkNodeC(ctx, t, db, nodes, func(ctx context.Context, tx *bolt.Tx, nc chan<- gc.Node) error {
|
||||
return references(ctx, tx, n, func(n gc.Node) {
|
||||
return c.references(ctx, tx, n, func(n gc.Node) {
|
||||
select {
|
||||
case nc <- n:
|
||||
case <-ctx.Done():
|
||||
@@ -386,6 +388,174 @@ func TestGCRefs(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCollectibleResources(t *testing.T) {
|
||||
db, cleanup, err := newDatabase(t)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
testResource := gc.ResourceType(0x10)
|
||||
defer cleanup()
|
||||
alters := []alterFunc{
|
||||
addContent("ns1", dgst(1), nil),
|
||||
addImage("ns1", "image1", dgst(1), nil),
|
||||
addContent("ns1", dgst(2), map[string]string{
|
||||
"containerd.io/gc.ref.test": "test2",
|
||||
}),
|
||||
addImage("ns1", "image2", dgst(2), nil),
|
||||
addLease("ns1", "lease1", labelmap(string(labelGCExpire), time.Now().Add(time.Hour).Format(time.RFC3339))),
|
||||
addLease("ns1", "lease2", labelmap(string(labelGCExpire), time.Now().Add(-1*time.Hour).Format(time.RFC3339))),
|
||||
}
|
||||
refs := map[gc.Node][]gc.Node{
|
||||
gcnode(ResourceContent, "ns1", dgst(1).String()): nil,
|
||||
gcnode(ResourceContent, "ns1", dgst(2).String()): {
|
||||
gcnode(testResource, "ns1", "test2"),
|
||||
},
|
||||
}
|
||||
all := []gc.Node{
|
||||
gcnode(ResourceContent, "ns1", dgst(1).String()),
|
||||
gcnode(ResourceContent, "ns1", dgst(2).String()),
|
||||
gcnode(ResourceLease, "ns1", "lease1"),
|
||||
gcnode(ResourceLease, "ns1", "lease2"),
|
||||
gcnode(testResource, "ns1", "test1"),
|
||||
gcnode(testResource, "ns1", "test2"), // 5: Will be removed
|
||||
gcnode(testResource, "ns1", "test3"),
|
||||
gcnode(testResource, "ns1", "test4"),
|
||||
}
|
||||
removeIndex := 5
|
||||
roots := []gc.Node{
|
||||
gcnode(ResourceContent, "ns1", dgst(1).String()),
|
||||
gcnode(ResourceContent, "ns1", dgst(2).String()),
|
||||
gcnode(ResourceLease, "ns1", "lease1"),
|
||||
gcnode(testResource, "ns1", "test1"),
|
||||
gcnode(testResource, "ns1", "test3"),
|
||||
}
|
||||
collector := &testCollector{
|
||||
all: []gc.Node{
|
||||
gcnode(testResource, "ns1", "test1"),
|
||||
gcnode(testResource, "ns1", "test2"),
|
||||
gcnode(testResource, "ns1", "test3"),
|
||||
gcnode(testResource, "ns1", "test4"),
|
||||
},
|
||||
active: []gc.Node{
|
||||
gcnode(testResource, "ns1", "test1"),
|
||||
},
|
||||
leased: map[string][]gc.Node{
|
||||
"lease1": {
|
||||
gcnode(testResource, "ns1", "test3"),
|
||||
},
|
||||
"lease2": {
|
||||
gcnode(testResource, "ns1", "test4"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if err := db.Update(func(tx *bolt.Tx) error {
|
||||
v1bkt, err := tx.CreateBucketIfNotExists(bucketKeyVersion)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, alter := range alters {
|
||||
if err := alter(v1bkt); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
t.Fatalf("Update failed: %+v", err)
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
c := startGCContext(ctx, map[gc.ResourceType]Collector{
|
||||
testResource: collector,
|
||||
})
|
||||
|
||||
for n, nodes := range refs {
|
||||
checkNodeC(ctx, t, db, nodes, func(ctx context.Context, tx *bolt.Tx, nc chan<- gc.Node) error {
|
||||
return c.references(ctx, tx, n, func(n gc.Node) {
|
||||
select {
|
||||
case nc <- n:
|
||||
case <-ctx.Done():
|
||||
}
|
||||
})
|
||||
})
|
||||
if t.Failed() {
|
||||
t.Fatalf("Failure scanning %v", n)
|
||||
}
|
||||
}
|
||||
checkNodes(ctx, t, db, all, func(ctx context.Context, tx *bolt.Tx, fn func(context.Context, gc.Node) error) error {
|
||||
return c.scanAll(ctx, tx, fn)
|
||||
})
|
||||
checkNodeC(ctx, t, db, roots, func(ctx context.Context, tx *bolt.Tx, nc chan<- gc.Node) error {
|
||||
return c.scanRoots(ctx, tx, nc)
|
||||
})
|
||||
|
||||
if err := db.Update(func(tx *bolt.Tx) error {
|
||||
if err := c.remove(ctx, tx, all[removeIndex]); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
t.Fatalf("Update failed: %+v", err)
|
||||
}
|
||||
all = append(all[:removeIndex], all[removeIndex+1:]...)
|
||||
checkNodes(ctx, t, db, all, func(ctx context.Context, tx *bolt.Tx, fn func(context.Context, gc.Node) error) error {
|
||||
return c.scanAll(ctx, tx, fn)
|
||||
})
|
||||
}
|
||||
|
||||
type testCollector struct {
|
||||
all []gc.Node
|
||||
active []gc.Node
|
||||
leased map[string][]gc.Node
|
||||
}
|
||||
|
||||
func (tc *testCollector) StartCollection(context.Context) (CollectionContext, error) {
|
||||
return tc, nil
|
||||
}
|
||||
|
||||
func (tc *testCollector) ReferenceLabel() string {
|
||||
return "test"
|
||||
}
|
||||
|
||||
func (tc *testCollector) All(fn func(gc.Node)) {
|
||||
for _, n := range tc.all {
|
||||
fn(n)
|
||||
}
|
||||
}
|
||||
|
||||
func (tc *testCollector) Active(namespace string, fn func(gc.Node)) {
|
||||
for _, n := range tc.active {
|
||||
if n.Namespace == namespace {
|
||||
fn(n)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (tc *testCollector) Leased(namespace, lease string, fn func(gc.Node)) {
|
||||
for _, n := range tc.leased[lease] {
|
||||
if n.Namespace == namespace {
|
||||
fn(n)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (tc *testCollector) Remove(n gc.Node) {
|
||||
for i := range tc.all {
|
||||
if tc.all[i] == n {
|
||||
tc.all = append(tc.all[:i], tc.all[i+1:]...)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (tc *testCollector) Cancel() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (tc *testCollector) Finish() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func newDatabase(t testing.TB) (*bolt.DB, func(), error) {
|
||||
td := t.TempDir()
|
||||
|
||||
@@ -400,6 +570,7 @@ func newDatabase(t testing.TB) (*bolt.DB, func(), error) {
|
||||
}
|
||||
|
||||
func checkNodeC(ctx context.Context, t *testing.T, db *bolt.DB, expected []gc.Node, fn func(context.Context, *bolt.Tx, chan<- gc.Node) error) {
|
||||
t.Helper()
|
||||
var actual []gc.Node
|
||||
nc := make(chan gc.Node)
|
||||
done := make(chan struct{})
|
||||
@@ -421,6 +592,7 @@ func checkNodeC(ctx context.Context, t *testing.T, db *bolt.DB, expected []gc.No
|
||||
}
|
||||
|
||||
func checkNodes(ctx context.Context, t *testing.T, db *bolt.DB, expected []gc.Node, fn func(context.Context, *bolt.Tx, func(context.Context, gc.Node) error) error) {
|
||||
t.Helper()
|
||||
var actual []gc.Node
|
||||
scanFn := func(ctx context.Context, n gc.Node) error {
|
||||
actual = append(actual, n)
|
||||
@@ -437,6 +609,7 @@ func checkNodes(ctx context.Context, t *testing.T, db *bolt.DB, expected []gc.No
|
||||
}
|
||||
|
||||
func checkNodesEqual(t *testing.T, n1, n2 []gc.Node) {
|
||||
t.Helper()
|
||||
sort.Sort(nodeList(n1))
|
||||
sort.Sort(nodeList(n2))
|
||||
|
||||
|
Reference in New Issue
Block a user