Add backreference support to mount manager

Signed-off-by: Derek McGowan <derek@mcg.dev>
This commit is contained in:
Derek McGowan
2025-07-01 14:45:29 -07:00
parent 9794addcea
commit 184fae60fc
3 changed files with 168 additions and 24 deletions

View File

@@ -72,4 +72,10 @@ var (
bucketKeyType = []byte("type")
bucketKeyMountedAt = []byte("mat")
bucketKeyMountPoint = []byte("mp")
bucketKeyLabels = []byte("labels")
labelGCContainerBackRef = []byte("containerd.io/gc.bref.container")
labelGCContentBackRef = []byte("containerd.io/gc.bref.content")
labelGCImageBackRef = []byte("containerd.io/gc.bref.image")
labelGCSnapBackRef = []byte("containerd.io/gc.bref.snapshot.")
)

View File

@@ -17,6 +17,7 @@
package manager
import (
"bytes"
"context"
"encoding/binary"
"errors"
@@ -35,6 +36,7 @@ import (
"github.com/containerd/containerd/v2/core/leases"
"github.com/containerd/containerd/v2/core/metadata"
"github.com/containerd/containerd/v2/core/metadata/boltutil"
"github.com/containerd/containerd/v2/core/mount"
"github.com/containerd/containerd/v2/pkg/gc"
"github.com/containerd/containerd/v2/pkg/namespaces"
@@ -69,15 +71,13 @@ func (mm *mountManager) Activate(ctx context.Context, name string, mounts []moun
}
lid, leased := leases.FromContext(ctx)
//if !leased {
// TODO: Set a default expiration? Otherwise will be immediately available for GC if nothing references
//}
var config mount.ActivateOptions
for _, opt := range opts {
opt(&config)
}
start := time.Now()
// highest index of a mount
// first system mount is the first index which should be mounted by the system
var firstSystemMount = -1
@@ -131,7 +131,7 @@ func (mm *mountManager) Activate(ctx context.Context, name string, mounts []moun
return mount.ActivationInfo{}, errdefs.ErrNotImplemented
}
// TODO: Get read lock to block GC context from starting
// Get read lock to block GC context from starting
mm.rwlock.RLock()
defer mm.rwlock.RUnlock()
@@ -170,12 +170,14 @@ func (mm *mountManager) Activate(ctx context.Context, name string, mounts []moun
return err
}
// Setup mounts now with generated targets
// TODO: Write created at time
// TODO: Write labels
// TODO: Store mount information including mountpoint
if err := boltutil.WriteLabels(bkt, config.Labels); err != nil {
return err
}
if err := boltutil.WriteTimestamps(bkt, start, start); err != nil {
return err
}
// TODO: Write lease
if leased {
if err = bkt.Put(bucketKeyLease, []byte(lid)); err != nil {
return err
@@ -194,14 +196,16 @@ func (mm *mountManager) Activate(ctx context.Context, name string, mounts []moun
}
}
// TODO: Store mount information including mountpoint
// Setup mounts now with generated targets
return nil
}); err != nil {
return mount.ActivationInfo{}, err
}
// TODO: If error, rollback and remove by name
defer func() {
// TODO: Any error should attempt to unmount all mounted
// If error, rollback and remove by name
if retErr != nil {
if err := mm.db.Update(func(tx *bolt.Tx) error {
v1bkt := tx.Bucket([]byte("v1"))
@@ -247,6 +251,7 @@ func (mm *mountManager) Activate(ctx context.Context, name string, mounts []moun
var mounted []mount.ActiveMount
defer func() {
// If error, unmount all mounted
if retErr != nil {
for i, m := range mounted {
var err error
@@ -377,6 +382,10 @@ func (mm *mountManager) Activate(ctx context.Context, name string, mounts []moun
}
if err := boltutil.WriteTimestamps(bkt, start, time.Now()); err != nil {
return err
}
// TODO: Save all system mounts
return nil
@@ -646,27 +655,94 @@ func (cc *collectionContext) All(fn func(gc.Node)) {
}
}
func (cc *collectionContext) Active(ns string, fn func(gc.Node)) {
nsbkt := getBucket(cc.tx, []byte("v1"), []byte(ns))
func gcnode(t gc.ResourceType, ns, key string) gc.Node {
return gc.Node{
Type: t,
Namespace: ns,
Key: key,
}
}
func (cc *collectionContext) ActiveWithBackReference(ns string, fn func(gc.Node), bref func(gc.Node, gc.Node)) {
nsbkt := getBucket(cc.tx, []byte("v1"), []byte(ns), bucketKeyMounts)
if nsbkt != nil {
// TODO: Check labels
mc := nsbkt.Cursor()
for mk, mv := mc.First(); mk != nil; mk, mv = mc.Next() {
if mv != nil {
continue
}
// TODO: Check for root/expire labels
/*
fn(gc.Node{
Type: metadata.ResourceMount,
Namespace: ns,
Key: string(mk),
})
*/
n := gcnode(metadata.ResourceMount, ns, string(mk))
lbkt := nsbkt.Bucket(mk).Bucket(bucketKeyLabels)
if lbkt != nil {
lc := lbkt.Cursor()
for _, h := range []struct {
key []byte
handler func([]byte, []byte)
}{
{
key: labelGCContainerBackRef,
handler: func(k, v []byte) {
if ks := string(k); ks != string(labelGCContainerBackRef) {
// Allow reference naming separated by . or /, ignore names
if ks[len(labelGCContainerBackRef)] != '.' && ks[len(labelGCContainerBackRef)] != '/' {
return
}
}
bref(gcnode(metadata.ResourceContainer, ns, string(v)), n)
},
},
{
key: labelGCContentBackRef,
handler: func(k, v []byte) {
if ks := string(k); ks != string(labelGCContentBackRef) {
// Allow reference naming separated by . or /, ignore names
if ks[len(labelGCContentBackRef)] != '.' && ks[len(labelGCContentBackRef)] != '/' {
return
}
}
bref(gcnode(metadata.ResourceContent, ns, string(v)), n)
},
},
{
key: labelGCImageBackRef,
handler: func(k, v []byte) {
if ks := string(k); ks != string(labelGCImageBackRef) {
// Allow reference naming separated by . or /, ignore names
if ks[len(labelGCImageBackRef)] != '.' && ks[len(labelGCImageBackRef)] != '/' {
return
}
}
bref(gcnode(metadata.ResourceImage, ns, string(v)), n)
},
},
{
key: labelGCSnapBackRef,
handler: func(k, v []byte) {
snapshotter := k[len(labelGCSnapBackRef):]
if i := bytes.IndexByte(snapshotter, '/'); i >= 0 {
snapshotter = snapshotter[:i]
}
bref(gcnode(metadata.ResourceSnapshot, ns, fmt.Sprintf("%s/%s", snapshotter, v)), n)
},
},
// TODO: Consider support for root/expire labels
} {
for k, v := lc.Seek(h.key); k != nil && bytes.HasPrefix(k, h.key); k, v = lc.Next() {
h.handler(k, v)
}
}
}
}
}
}
func (cc *collectionContext) Active(ns string, fn func(gc.Node)) {
cc.ActiveWithBackReference(ns, fn, func(gc.Node, gc.Node) {})
}
func (cc *collectionContext) Leased(ns, lease string, fn func(gc.Node)) {
bkt := getBucket(cc.tx, []byte("v1"), []byte(ns), []byte("leases"), []byte(lease))
if bkt != nil {

View File

@@ -144,8 +144,11 @@ func (h *errOnceHandler) Unmount(_ context.Context, mp string) error {
func TestGC(t *testing.T) {
type gcrun struct {
a []mount.Mount
o []mount.ActivateOpt
d []string
all []string
active []string
brefs map[string][]string
remove []string
gcErr bool
}
@@ -193,6 +196,41 @@ func TestGC(t *testing.T) {
{}, // Run again without error to bring mount count back to zero
},
},
{
name: "ActiveBrefs",
gcruns: []gcrun{
{
a: []mount.Mount{
{
Type: "noop",
},
{
Type: "noop",
},
},
o: []mount.ActivateOpt{
mount.WithLabels(map[string]string{"containerd.io/gc.bref.container": "container1"}),
},
all: []string{"0-0", "0-1"},
brefs: map[string][]string{
"container1": {"0-0", "0-1"},
},
remove: []string{"0-1"},
},
{
all: []string{"0-0"},
remove: []string{"0-0"},
brefs: map[string][]string{
"container1": {"0-0"},
},
},
{
brefs: map[string][]string{
"container1": nil,
},
},
},
},
} {
t.Run(tc.name, func(t *testing.T) {
td := t.TempDir()
@@ -218,7 +256,7 @@ func TestGC(t *testing.T) {
for i, run := range tc.gcruns {
for j, mnt := range run.a {
id := fmt.Sprintf("%d-%d", i, j)
m.Activate(ctx, id, []mount.Mount{mnt})
m.Activate(ctx, id, []mount.Mount{mnt}, run.o...)
}
for _, id := range run.d {
@@ -234,6 +272,8 @@ func TestGC(t *testing.T) {
var all []string
checkGCActive(t, i, cc, run.active, run.brefs)
cc.All(func(n gc.Node) {
all = append(all, n.Key)
})
@@ -267,7 +307,29 @@ func TestGC(t *testing.T) {
}
}
// TODO: Test formatting
func checkGCActive(t *testing.T, i int, cc metadata.CollectionContext, active []string, brefs map[string][]string) {
t.Helper()
ccb := cc.(interface {
ActiveWithBackReference(string, func(gc.Node), func(gc.Node, gc.Node))
})
var (
activeKeys []string
activeBrefs = map[string][]string{}
)
ccb.ActiveWithBackReference("test", func(n gc.Node) {
activeKeys = append(activeKeys, n.Key)
}, func(n, ref gc.Node) {
activeBrefs[n.Key] = append(activeBrefs[n.Key], ref.Key)
})
require.Equal(t, active, activeKeys, "run %d: active does not match", i)
for k := range brefs {
require.Equal(t, brefs[k], activeBrefs[k], "run %d: brefs for %q does not match", i, k)
}
}
// TODO: Test Info
// TODO: Test deactivate
// TODO: Test Sync