Skip to content

Commit c77c89b

Browse files
committed
Add lease expiration to garbage collection
Allow setting an expiration label to have the garbage collector remove an item after the specified time. Signed-off-by: Derek McGowan <[email protected]>
1 parent 02579c8 commit c77c89b

File tree

4 files changed

+100
-3
lines changed

4 files changed

+100
-3
lines changed

lease.go

+10-3
Original file line numberDiff line numberDiff line change
@@ -35,16 +35,22 @@ type Lease struct {
3535
}
3636

3737
// CreateLease creates a new lease
38+
// TODO: Add variadic lease opt
3839
func (c *Client) CreateLease(ctx context.Context) (Lease, error) {
3940
lapi := c.LeasesService()
40-
resp, err := lapi.Create(ctx, &leasesapi.CreateRequest{})
41+
labels := map[string]string{
42+
"containerd.io/gc.expire": time.Now().Add(24 * 3600 * time.Second).Format(time.RFC3339),
43+
}
44+
resp, err := lapi.Create(ctx, &leasesapi.CreateRequest{labels})
4145
if err != nil {
4246
return Lease{}, err
4347
}
4448

4549
return Lease{
46-
id: resp.Lease.ID,
47-
client: c,
50+
id: resp.Lease.ID,
51+
createdAt: resp.Lease.CreatedAt,
52+
labels: labels,
53+
client: c,
4854
}, nil
4955
}
5056

@@ -60,6 +66,7 @@ func (c *Client) ListLeases(ctx context.Context) ([]Lease, error) {
6066
leases[i] = Lease{
6167
id: resp.Leases[i].ID,
6268
createdAt: resp.Leases[i].CreatedAt,
69+
labels: resp.Leases[i].Labels,
6370
client: c,
6471
}
6572
}

leases/lease.go

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package leases
2+
3+
import "time"
4+
5+
type LeaseOpt func(*Lease)
6+
7+
type LeaseManager interface {
8+
Create(...LeaseOpt) (Lease, error)
9+
Delete(Lease) error
10+
List(...string) ([]Lease, error)
11+
}
12+
13+
type Lease struct {
14+
ID string
15+
CreatedAt time.Time
16+
Labels map[string]string
17+
}

metadata/gc.go

+47
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"context"
2222
"fmt"
2323
"strings"
24+
"time"
2425

2526
"github.com/boltdb/bolt"
2627
"github.com/containerd/containerd/gc"
@@ -39,12 +40,15 @@ const (
3940
ResourceContainer
4041
// ResourceTask specifies a task resource
4142
ResourceTask
43+
// ResourceLease specifies a lease
44+
ResourceLease
4245
)
4346

4447
var (
4548
labelGCRoot = []byte("containerd.io/gc.root")
4649
labelGCSnapRef = []byte("containerd.io/gc.ref.snapshot.")
4750
labelGCContentRef = []byte("containerd.io/gc.ref.content")
51+
labelGCExpire = []byte("containerd.io/gc.expire")
4852
)
4953

5054
func scanRoots(ctx context.Context, tx *bolt.Tx, nc chan<- gc.Node) error {
@@ -53,6 +57,8 @@ func scanRoots(ctx context.Context, tx *bolt.Tx, nc chan<- gc.Node) error {
5357
return nil
5458
}
5559

60+
expThreshold := time.Now()
61+
5662
// iterate through each namespace
5763
v1c := v1bkt.Cursor()
5864

@@ -71,6 +77,30 @@ func scanRoots(ctx context.Context, tx *bolt.Tx, nc chan<- gc.Node) error {
7177
}
7278
libkt := lbkt.Bucket(k)
7379

80+
if lblbkt := libkt.Bucket(bucketKeyObjectLabels); lblbkt != nil {
81+
if expV := lblbkt.Get(labelGCExpire); expV != nil {
82+
exp, err := time.Parse(time.RFC3339, string(expV))
83+
if err != nil {
84+
// label not used, log and continue to use lease
85+
log.G(ctx).WithError(err).WithField("lease", string(k)).Infof("ignoring invalid expiration value %q", string(expV))
86+
} else if expThreshold.After(exp) {
87+
// lease has expired, skip
88+
return nil
89+
}
90+
}
91+
}
92+
93+
select {
94+
case nc <- gcnode(ResourceLease, ns, string(k)):
95+
case <-ctx.Done():
96+
return ctx.Err()
97+
}
98+
99+
// Emit content and snapshots as roots instead of implementing
100+
// in references. Since leases cannot be referenced there is
101+
// no need to allow the lookup to be recursive, handling here
102+
// therefore reduces the number of database seeks.
103+
74104
cbkt := libkt.Bucket(bucketKeyObjectContent)
75105
if cbkt != nil {
76106
if err := cbkt.ForEach(func(k, v []byte) error {
@@ -261,6 +291,18 @@ func scanAll(ctx context.Context, tx *bolt.Tx, fn func(ctx context.Context, n gc
261291
nbkt := v1bkt.Bucket(k)
262292
ns := string(k)
263293

294+
lbkt := nbkt.Bucket(bucketKeyObjectLeases)
295+
if lbkt != nil {
296+
if err := lbkt.ForEach(func(k, v []byte) error {
297+
if v != nil {
298+
return nil
299+
}
300+
return fn(ctx, gcnode(ResourceLease, ns, string(k)))
301+
}); err != nil {
302+
return err
303+
}
304+
}
305+
264306
sbkt := nbkt.Bucket(bucketKeyObjectSnapshots)
265307
if sbkt != nil {
266308
if err := sbkt.ForEach(func(sk, sv []byte) error {
@@ -334,6 +376,11 @@ func remove(ctx context.Context, tx *bolt.Tx, node gc.Node) error {
334376
return ssbkt.DeleteBucket([]byte(parts[1]))
335377
}
336378
}
379+
case ResourceLease:
380+
lbkt := nsbkt.Bucket(bucketKeyObjectLeases)
381+
if lbkt != nil {
382+
return lbkt.DeleteBucket([]byte(node.Key))
383+
}
337384
}
338385

339386
return nil

metadata/gc_test.go

+26
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import (
2525
"path/filepath"
2626
"sort"
2727
"testing"
28+
"time"
2829

2930
"github.com/boltdb/bolt"
3031
"github.com/containerd/containerd/gc"
@@ -63,6 +64,12 @@ func TestGCRoots(t *testing.T) {
6364
addLeaseSnapshot("ns2", "l2", "overlay", "sn6"),
6465
addLeaseContent("ns2", "l1", dgst(4)),
6566
addLeaseContent("ns2", "l2", dgst(5)),
67+
addLease("ns2", "l3", labelmap(string(labelGCExpire), time.Now().Add(3600*time.Second).Format(time.RFC3339))),
68+
addLeaseContent("ns2", "l3", dgst(6)),
69+
addLeaseSnapshot("ns2", "l3", "overlay", "sn7"),
70+
addLease("ns2", "l4", labelmap(string(labelGCExpire), time.Now().Format(time.RFC3339))),
71+
addLeaseContent("ns2", "l4", dgst(7)),
72+
addLeaseSnapshot("ns2", "l4", "overlay", "sn8"),
6673
}
6774

6875
expected := []gc.Node{
@@ -71,6 +78,7 @@ func TestGCRoots(t *testing.T) {
7178
gcnode(ResourceContent, "ns2", dgst(2).String()),
7279
gcnode(ResourceContent, "ns2", dgst(4).String()),
7380
gcnode(ResourceContent, "ns2", dgst(5).String()),
81+
gcnode(ResourceContent, "ns2", dgst(6).String()),
7482
gcnode(ResourceSnapshot, "ns1", "overlay/sn2"),
7583
gcnode(ResourceSnapshot, "ns1", "overlay/sn3"),
7684
gcnode(ResourceSnapshot, "ns1", "overlay/sn4"),
@@ -81,6 +89,10 @@ func TestGCRoots(t *testing.T) {
8189
gcnode(ResourceSnapshot, "ns1", "overlay/sn9"),
8290
gcnode(ResourceSnapshot, "ns2", "overlay/sn5"),
8391
gcnode(ResourceSnapshot, "ns2", "overlay/sn6"),
92+
gcnode(ResourceSnapshot, "ns2", "overlay/sn7"),
93+
gcnode(ResourceLease, "ns2", "l1"),
94+
gcnode(ResourceLease, "ns2", "l2"),
95+
gcnode(ResourceLease, "ns2", "l3"),
8496
}
8597

8698
if err := db.Update(func(tx *bolt.Tx) error {
@@ -126,6 +138,8 @@ func TestGCRemove(t *testing.T) {
126138
addSnapshot("ns1", "overlay", "sn3", "", labelmap(string(labelGCRoot), "always")),
127139
addSnapshot("ns1", "overlay", "sn4", "", nil),
128140
addSnapshot("ns2", "overlay", "sn1", "", nil),
141+
addLease("ns1", "l1", labelmap(string(labelGCExpire), time.Now().Add(3600*time.Second).Format(time.RFC3339))),
142+
addLease("ns2", "l2", labelmap(string(labelGCExpire), time.Now().Format(time.RFC3339))),
129143
}
130144

131145
all := []gc.Node{
@@ -139,6 +153,8 @@ func TestGCRemove(t *testing.T) {
139153
gcnode(ResourceSnapshot, "ns1", "overlay/sn3"),
140154
gcnode(ResourceSnapshot, "ns1", "overlay/sn4"),
141155
gcnode(ResourceSnapshot, "ns2", "overlay/sn1"),
156+
gcnode(ResourceLease, "ns1", "l1"),
157+
gcnode(ResourceLease, "ns2", "l2"),
142158
}
143159

144160
var deleted, remaining []gc.Node
@@ -425,6 +441,16 @@ func addContent(ns string, dgst digest.Digest, labels map[string]string) alterFu
425441
}
426442
}
427443

444+
func addLease(ns, lid string, labels map[string]string) alterFunc {
445+
return func(bkt *bolt.Bucket) error {
446+
lbkt, err := createBuckets(bkt, ns, string(bucketKeyObjectLeases), lid)
447+
if err != nil {
448+
return err
449+
}
450+
return boltutil.WriteLabels(lbkt, labels)
451+
}
452+
}
453+
428454
func addLeaseSnapshot(ns, lid, snapshotter, name string) alterFunc {
429455
return func(bkt *bolt.Bucket) error {
430456
sbkt, err := createBuckets(bkt, ns, string(bucketKeyObjectLeases), lid, string(bucketKeyObjectSnapshots), snapshotter)

0 commit comments

Comments
 (0)