Skip to content

Commit 0d52c71

Browse files
Merge pull request #2474 from dmcgowan/lease-expiration
Improved lease management
2 parents 394784b + 29b72d4 commit 0d52c71

21 files changed

Lines changed: 765 additions & 199 deletions

File tree

client.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ import (
4343
"github.com/containerd/containerd/errdefs"
4444
"github.com/containerd/containerd/events"
4545
"github.com/containerd/containerd/images"
46+
"github.com/containerd/containerd/leases"
47+
leasesproxy "github.com/containerd/containerd/leases/proxy"
4648
"github.com/containerd/containerd/namespaces"
4749
"github.com/containerd/containerd/pkg/dialer"
4850
"github.com/containerd/containerd/plugin"
@@ -512,11 +514,11 @@ func (c *Client) IntrospectionService() introspectionapi.IntrospectionClient {
512514
}
513515

514516
// LeasesService returns the underlying Leases Client
515-
func (c *Client) LeasesService() leasesapi.LeasesClient {
517+
func (c *Client) LeasesService() leases.Manager {
516518
if c.leasesService != nil {
517519
return c.leasesService
518520
}
519-
return leasesapi.NewLeasesClient(c.conn)
521+
return leasesproxy.NewLeaseManager(leasesapi.NewLeasesClient(c.conn))
520522
}
521523

522524
// HealthService returns the underlying GRPC HealthClient

cmd/ctr/app/main.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424
"github.com/containerd/containerd/cmd/ctr/commands/content"
2525
"github.com/containerd/containerd/cmd/ctr/commands/events"
2626
"github.com/containerd/containerd/cmd/ctr/commands/images"
27+
"github.com/containerd/containerd/cmd/ctr/commands/leases"
2728
namespacesCmd "github.com/containerd/containerd/cmd/ctr/commands/namespaces"
2829
"github.com/containerd/containerd/cmd/ctr/commands/plugins"
2930
"github.com/containerd/containerd/cmd/ctr/commands/pprof"
@@ -96,6 +97,7 @@ containerd CLI
9697
content.Command,
9798
events.Command,
9899
images.Command,
100+
leases.Command,
99101
namespacesCmd.Command,
100102
pprof.Command,
101103
run.Command,

cmd/ctr/commands/leases/leases.go

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
/*
2+
Copyright The containerd Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package leases
18+
19+
import (
20+
"fmt"
21+
"os"
22+
"sort"
23+
"strings"
24+
"text/tabwriter"
25+
"time"
26+
27+
"github.com/containerd/containerd/cmd/ctr/commands"
28+
"github.com/containerd/containerd/leases"
29+
"github.com/pkg/errors"
30+
"github.com/urfave/cli"
31+
)
32+
33+
// Command is the cli command for managing content
34+
var Command = cli.Command{
35+
Name: "leases",
36+
Usage: "manage leases",
37+
Subcommands: cli.Commands{
38+
listCommand,
39+
createCommand,
40+
deleteCommand,
41+
},
42+
}
43+
44+
var listCommand = cli.Command{
45+
46+
Name: "list",
47+
Aliases: []string{"ls"},
48+
Usage: "list all active leases",
49+
ArgsUsage: "[flags] <filter>",
50+
Description: "list active leases by containerd",
51+
Flags: []cli.Flag{
52+
cli.BoolFlag{
53+
Name: "quiet, q",
54+
Usage: "print only the blob digest",
55+
},
56+
},
57+
Action: func(context *cli.Context) error {
58+
var (
59+
filters = context.Args()
60+
quiet = context.Bool("quiet")
61+
)
62+
client, ctx, cancel, err := commands.NewClient(context)
63+
if err != nil {
64+
return err
65+
}
66+
defer cancel()
67+
68+
ls := client.LeasesService()
69+
70+
leaseList, err := ls.List(ctx, filters...)
71+
if err != nil {
72+
return errors.Wrap(err, "failed to list leases")
73+
}
74+
if quiet {
75+
for _, l := range leaseList {
76+
fmt.Println(l.ID)
77+
}
78+
return nil
79+
}
80+
tw := tabwriter.NewWriter(os.Stdout, 1, 8, 1, ' ', 0)
81+
fmt.Fprintln(tw, "ID\tCREATED AT\tLABELS\t")
82+
for _, l := range leaseList {
83+
labels := "-"
84+
if len(l.Labels) > 0 {
85+
var pairs []string
86+
for k, v := range l.Labels {
87+
pairs = append(pairs, fmt.Sprintf("%v=%v", k, v))
88+
}
89+
sort.Strings(pairs)
90+
labels = strings.Join(pairs, ",")
91+
}
92+
93+
fmt.Fprintf(tw, "%v\t%v\t%s\t\n",
94+
l.ID,
95+
l.CreatedAt.Local().Format(time.RFC3339),
96+
labels)
97+
}
98+
99+
return tw.Flush()
100+
},
101+
}
102+
103+
var createCommand = cli.Command{
104+
Name: "create",
105+
Usage: "create lease",
106+
ArgsUsage: "[flags] <label>=<value> ...",
107+
Description: "create a new lease",
108+
Flags: []cli.Flag{
109+
cli.StringFlag{
110+
Name: "id",
111+
Usage: "set the id for the lease, will be generated by default",
112+
},
113+
cli.DurationFlag{
114+
Name: "expires, x",
115+
Usage: "expiration of lease (0 value will not expire)",
116+
Value: 24 * 3600 * time.Second,
117+
},
118+
},
119+
Action: func(context *cli.Context) error {
120+
var labelstr = context.Args()
121+
client, ctx, cancel, err := commands.NewClient(context)
122+
if err != nil {
123+
return err
124+
}
125+
defer cancel()
126+
127+
ls := client.LeasesService()
128+
opts := []leases.Opt{}
129+
if len(labelstr) > 0 {
130+
labels := map[string]string{}
131+
for _, lstr := range labelstr {
132+
l := strings.SplitN(lstr, "=", 2)
133+
if len(l) == 1 {
134+
labels[l[0]] = ""
135+
} else {
136+
labels[l[0]] = l[1]
137+
}
138+
}
139+
opts = append(opts, leases.WithLabels(labels))
140+
}
141+
142+
if id := context.String("id"); id != "" {
143+
opts = append(opts, leases.WithID(id))
144+
}
145+
if exp := context.Duration("expires"); exp > 0 {
146+
opts = append(opts, leases.WithExpiration(exp))
147+
}
148+
149+
l, err := ls.Create(ctx, opts...)
150+
if err != nil {
151+
return err
152+
}
153+
154+
fmt.Println(l.ID)
155+
156+
return nil
157+
},
158+
}
159+
160+
var deleteCommand = cli.Command{
161+
Name: "delete",
162+
Aliases: []string{"rm"},
163+
Usage: "delete a lease",
164+
ArgsUsage: "[flags] <lease id> ...",
165+
Description: "delete a lease",
166+
Action: func(context *cli.Context) error {
167+
var lids = context.Args()
168+
if len(lids) == 0 {
169+
return cli.ShowSubcommandHelp(context)
170+
}
171+
client, ctx, cancel, err := commands.NewClient(context)
172+
if err != nil {
173+
return err
174+
}
175+
defer cancel()
176+
177+
ls := client.LeasesService()
178+
for _, lid := range lids {
179+
lease := leases.Lease{
180+
ID: lid,
181+
}
182+
if err := ls.Delete(ctx, lease); err != nil {
183+
return err
184+
}
185+
fmt.Println(lid)
186+
}
187+
188+
return nil
189+
},
190+
}

lease.go

Lines changed: 6 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -20,89 +20,27 @@ import (
2020
"context"
2121
"time"
2222

23-
leasesapi "github.com/containerd/containerd/api/services/leases/v1"
2423
"github.com/containerd/containerd/leases"
2524
)
2625

27-
// Lease is used to hold a reference to active resources which have not been
28-
// referenced by a root resource. This is useful for preventing garbage
29-
// collection of resources while they are actively being updated.
30-
type Lease struct {
31-
id string
32-
createdAt time.Time
33-
34-
client *Client
35-
}
36-
37-
// CreateLease creates a new lease
38-
func (c *Client) CreateLease(ctx context.Context) (Lease, error) {
39-
lapi := c.LeasesService()
40-
resp, err := lapi.Create(ctx, &leasesapi.CreateRequest{})
41-
if err != nil {
42-
return Lease{}, err
43-
}
44-
45-
return Lease{
46-
id: resp.Lease.ID,
47-
client: c,
48-
}, nil
49-
}
50-
51-
// ListLeases lists active leases
52-
func (c *Client) ListLeases(ctx context.Context) ([]Lease, error) {
53-
lapi := c.LeasesService()
54-
resp, err := lapi.List(ctx, &leasesapi.ListRequest{})
55-
if err != nil {
56-
return nil, err
57-
}
58-
leases := make([]Lease, len(resp.Leases))
59-
for i := range resp.Leases {
60-
leases[i] = Lease{
61-
id: resp.Leases[i].ID,
62-
createdAt: resp.Leases[i].CreatedAt,
63-
client: c,
64-
}
65-
}
66-
67-
return leases, nil
68-
}
69-
7026
// WithLease attaches a lease on the context
7127
func (c *Client) WithLease(ctx context.Context) (context.Context, func(context.Context) error, error) {
72-
_, ok := leases.Lease(ctx)
28+
_, ok := leases.FromContext(ctx)
7329
if ok {
7430
return ctx, func(context.Context) error {
7531
return nil
7632
}, nil
7733
}
7834

79-
l, err := c.CreateLease(ctx)
35+
ls := c.LeasesService()
36+
37+
l, err := ls.Create(ctx, leases.WithRandomID(), leases.WithExpiration(24*3600*time.Second))
8038
if err != nil {
8139
return nil, nil, err
8240
}
8341

84-
ctx = leases.WithLease(ctx, l.ID())
42+
ctx = leases.WithLease(ctx, l.ID)
8543
return ctx, func(ctx context.Context) error {
86-
return l.Delete(ctx)
44+
return ls.Delete(ctx, l)
8745
}, nil
8846
}
89-
90-
// ID returns the lease ID
91-
func (l Lease) ID() string {
92-
return l.id
93-
}
94-
95-
// CreatedAt returns the time at which the lease was created
96-
func (l Lease) CreatedAt() time.Time {
97-
return l.createdAt
98-
}
99-
100-
// Delete deletes the lease, removing the reference to all resources created
101-
// during the lease.
102-
func (l Lease) Delete(ctx context.Context) error {
103-
lapi := l.client.LeasesService()
104-
_, err := lapi.Delete(ctx, &leasesapi.DeleteRequest{
105-
ID: l.id,
106-
})
107-
return err
108-
}

leases/context.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@ func WithLease(ctx context.Context, lid string) context.Context {
2929
return withGRPCLeaseHeader(ctx, lid)
3030
}
3131

32-
// Lease returns the lease from the context.
33-
func Lease(ctx context.Context) (string, bool) {
32+
// FromContext returns the lease from the context.
33+
func FromContext(ctx context.Context) (string, bool) {
3434
lid, ok := ctx.Value(leaseKey{}).(string)
3535
if !ok {
3636
return fromGRPCHeader(ctx)

leases/id.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
Copyright The containerd Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package leases
18+
19+
import (
20+
"encoding/base64"
21+
"fmt"
22+
"math/rand"
23+
"time"
24+
)
25+
26+
// WithRandomID sets the lease ID to a random unique value
27+
func WithRandomID() Opt {
28+
return func(l *Lease) error {
29+
t := time.Now()
30+
var b [3]byte
31+
rand.Read(b[:])
32+
l.ID = fmt.Sprintf("%d-%s", t.Nanosecond(), base64.URLEncoding.EncodeToString(b[:]))
33+
return nil
34+
}
35+
}
36+
37+
// WithID sets the ID for the lease
38+
func WithID(id string) Opt {
39+
return func(l *Lease) error {
40+
l.ID = id
41+
return nil
42+
}
43+
}

0 commit comments

Comments
 (0)