Add leases manager interface

Add leases manager to the leases package and use the
interface on the client and service.

Signed-off-by: Derek McGowan <derek@mcgstyle.net>
This commit is contained in:
Derek McGowan 2018-07-12 10:48:59 -07:00
parent c77c89b3d1
commit 8cf3fad8d4
No known key found for this signature in database
GPG Key ID: F58C5D0A4405ACDB
12 changed files with 304 additions and 205 deletions

View File

@ -43,6 +43,8 @@ import (
"github.com/containerd/containerd/errdefs" "github.com/containerd/containerd/errdefs"
"github.com/containerd/containerd/events" "github.com/containerd/containerd/events"
"github.com/containerd/containerd/images" "github.com/containerd/containerd/images"
"github.com/containerd/containerd/leases"
leasesproxy "github.com/containerd/containerd/leases/proxy"
"github.com/containerd/containerd/namespaces" "github.com/containerd/containerd/namespaces"
"github.com/containerd/containerd/pkg/dialer" "github.com/containerd/containerd/pkg/dialer"
"github.com/containerd/containerd/plugin" "github.com/containerd/containerd/plugin"
@ -508,11 +510,11 @@ func (c *Client) IntrospectionService() introspectionapi.IntrospectionClient {
} }
// LeasesService returns the underlying Leases Client // LeasesService returns the underlying Leases Client
func (c *Client) LeasesService() leasesapi.LeasesClient { func (c *Client) LeasesService() leases.Manager {
if c.leasesService != nil { if c.leasesService != nil {
return c.leasesService return c.leasesService
} }
return leasesapi.NewLeasesClient(c.conn) return leasesproxy.NewLeaseManager(leasesapi.NewLeasesClient(c.conn))
} }
// HealthService returns the underlying GRPC HealthClient // HealthService returns the underlying GRPC HealthClient

View File

@ -20,96 +20,27 @@ import (
"context" "context"
"time" "time"
leasesapi "github.com/containerd/containerd/api/services/leases/v1"
"github.com/containerd/containerd/leases" "github.com/containerd/containerd/leases"
) )
// Lease is used to hold a reference to active resources which have not been
// referenced by a root resource. This is useful for preventing garbage
// collection of resources while they are actively being updated.
type Lease struct {
id string
createdAt time.Time
client *Client
}
// CreateLease creates a new lease
// TODO: Add variadic lease opt
func (c *Client) CreateLease(ctx context.Context) (Lease, error) {
lapi := c.LeasesService()
labels := map[string]string{
"containerd.io/gc.expire": time.Now().Add(24 * 3600 * time.Second).Format(time.RFC3339),
}
resp, err := lapi.Create(ctx, &leasesapi.CreateRequest{labels})
if err != nil {
return Lease{}, err
}
return Lease{
id: resp.Lease.ID,
createdAt: resp.Lease.CreatedAt,
labels: labels,
client: c,
}, nil
}
// ListLeases lists active leases
func (c *Client) ListLeases(ctx context.Context) ([]Lease, error) {
lapi := c.LeasesService()
resp, err := lapi.List(ctx, &leasesapi.ListRequest{})
if err != nil {
return nil, err
}
leases := make([]Lease, len(resp.Leases))
for i := range resp.Leases {
leases[i] = Lease{
id: resp.Leases[i].ID,
createdAt: resp.Leases[i].CreatedAt,
labels: resp.Leases[i].Labels,
client: c,
}
}
return leases, nil
}
// WithLease attaches a lease on the context // WithLease attaches a lease on the context
func (c *Client) WithLease(ctx context.Context) (context.Context, func(context.Context) error, error) { func (c *Client) WithLease(ctx context.Context) (context.Context, func(context.Context) error, error) {
_, ok := leases.Lease(ctx) _, ok := leases.FromContext(ctx)
if ok { if ok {
return ctx, func(context.Context) error { return ctx, func(context.Context) error {
return nil return nil
}, nil }, nil
} }
l, err := c.CreateLease(ctx) ls := c.LeasesService()
l, err := ls.Create(ctx, leases.WithRandomID(), leases.WithExpiration(24*3600*time.Second))
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
ctx = leases.WithLease(ctx, l.ID()) ctx = leases.WithLease(ctx, l.ID)
return ctx, func(ctx context.Context) error { return ctx, func(ctx context.Context) error {
return l.Delete(ctx) return ls.Delete(ctx, l)
}, nil }, nil
} }
// ID returns the lease ID
func (l Lease) ID() string {
return l.id
}
// CreatedAt returns the time at which the lease was created
func (l Lease) CreatedAt() time.Time {
return l.createdAt
}
// Delete deletes the lease, removing the reference to all resources created
// during the lease.
func (l Lease) Delete(ctx context.Context) error {
lapi := l.client.LeasesService()
_, err := lapi.Delete(ctx, &leasesapi.DeleteRequest{
ID: l.id,
})
return err
}

View File

@ -29,8 +29,8 @@ func WithLease(ctx context.Context, lid string) context.Context {
return withGRPCLeaseHeader(ctx, lid) return withGRPCLeaseHeader(ctx, lid)
} }
// Lease returns the lease from the context. // FromContext returns the lease from the context.
func Lease(ctx context.Context) (string, bool) { func FromContext(ctx context.Context) (string, bool) {
lid, ok := ctx.Value(leaseKey{}).(string) lid, ok := ctx.Value(leaseKey{}).(string)
if !ok { if !ok {
return fromGRPCHeader(ctx) return fromGRPCHeader(ctx)

43
leases/id.go Normal file
View File

@ -0,0 +1,43 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package leases
import (
"encoding/base64"
"fmt"
"math/rand"
"time"
)
// WithRandomID sets the lease ID to a random unique value
func WithRandomID() Opt {
return func(l *Lease) error {
t := time.Now()
var b [3]byte
rand.Read(b[:])
l.ID = fmt.Sprintf("%d-%s", t.Nanosecond(), base64.URLEncoding.EncodeToString(b[:]))
return nil
}
}
// WithID sets the ID for the lease
func WithID(id string) Opt {
return func(l *Lease) error {
l.ID = id
return nil
}
}

View File

@ -1,17 +1,60 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package leases package leases
import "time" import (
"context"
"time"
)
type LeaseOpt func(*Lease) // Opt is used to set options on a lease
type Opt func(*Lease) error
type LeaseManager interface { // Manager is used to create, list, and remove leases
Create(...LeaseOpt) (Lease, error) type Manager interface {
Delete(Lease) error Create(context.Context, ...Opt) (Lease, error)
List(...string) ([]Lease, error) Delete(context.Context, Lease) error
List(context.Context, ...string) ([]Lease, error)
} }
// Lease retains resources to prevent cleanup before
// the resources can be fully referenced.
type Lease struct { type Lease struct {
ID string ID string
CreatedAt time.Time CreatedAt time.Time
Labels map[string]string Labels map[string]string
} }
// WithLabels sets labels on a lease
func WithLabels(labels map[string]string) Opt {
return func(l *Lease) error {
l.Labels = labels
return nil
}
}
// WithExpiration sets an expiration on the lease
func WithExpiration(d time.Duration) Opt {
return func(l *Lease) error {
if l.Labels == nil {
l.Labels = map[string]string{}
}
l.Labels["containerd.io/gc.expire"] = time.Now().Add(d).Format(time.RFC3339)
return nil
}
}

84
leases/proxy/manager.go Normal file
View File

@ -0,0 +1,84 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package proxy
import (
"context"
leasesapi "github.com/containerd/containerd/api/services/leases/v1"
"github.com/containerd/containerd/leases"
)
type proxyManager struct {
client leasesapi.LeasesClient
}
// NewLeaseManager returns a lease manager which communicates
// through a grpc lease service.
func NewLeaseManager(client leasesapi.LeasesClient) leases.Manager {
return &proxyManager{
client: client,
}
}
func (pm *proxyManager) Create(ctx context.Context, opts ...leases.Opt) (leases.Lease, error) {
l := leases.Lease{}
for _, opt := range opts {
if err := opt(&l); err != nil {
return leases.Lease{}, err
}
}
resp, err := pm.client.Create(ctx, &leasesapi.CreateRequest{
ID: l.ID,
Labels: l.Labels,
})
if err != nil {
return leases.Lease{}, err
}
return leases.Lease{
ID: resp.Lease.ID,
CreatedAt: resp.Lease.CreatedAt,
Labels: resp.Lease.Labels,
}, nil
}
func (pm *proxyManager) Delete(ctx context.Context, l leases.Lease) error {
_, err := pm.client.Delete(ctx, &leasesapi.DeleteRequest{
ID: l.ID,
})
return err
}
func (pm *proxyManager) List(ctx context.Context, filters ...string) ([]leases.Lease, error) {
resp, err := pm.client.List(ctx, &leasesapi.ListRequest{
Filters: filters,
})
if err != nil {
return nil, err
}
l := make([]leases.Lease, len(resp.Leases))
for i := range resp.Leases {
l[i] = leases.Lease{
ID: resp.Leases[i].ID,
CreatedAt: resp.Leases[i].CreatedAt,
Labels: resp.Leases[i].Labels,
}
}
return l, nil
}

View File

@ -29,17 +29,6 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
) )
// Lease retains resources to prevent garbage collection before
// the resources can be fully referenced.
type Lease struct {
ID string
CreatedAt time.Time
Labels map[string]string
Content []string
Snapshots map[string][]string
}
// LeaseManager manages the create/delete lifecyle of leases // LeaseManager manages the create/delete lifecyle of leases
// and also returns existing leases // and also returns existing leases
type LeaseManager struct { type LeaseManager struct {
@ -55,49 +44,56 @@ func NewLeaseManager(tx *bolt.Tx) *LeaseManager {
} }
// Create creates a new lease using the provided lease // Create creates a new lease using the provided lease
func (lm *LeaseManager) Create(ctx context.Context, lid string, labels map[string]string) (Lease, error) { func (lm *LeaseManager) Create(ctx context.Context, opts ...leases.Opt) (leases.Lease, error) {
var l leases.Lease
for _, opt := range opts {
if err := opt(&l); err != nil {
return leases.Lease{}, err
}
}
if l.ID == "" {
return leases.Lease{}, errors.New("lease id must be provided")
}
namespace, err := namespaces.NamespaceRequired(ctx) namespace, err := namespaces.NamespaceRequired(ctx)
if err != nil { if err != nil {
return Lease{}, err return leases.Lease{}, err
} }
topbkt, err := createBucketIfNotExists(lm.tx, bucketKeyVersion, []byte(namespace), bucketKeyObjectLeases) topbkt, err := createBucketIfNotExists(lm.tx, bucketKeyVersion, []byte(namespace), bucketKeyObjectLeases)
if err != nil { if err != nil {
return Lease{}, err return leases.Lease{}, err
} }
txbkt, err := topbkt.CreateBucket([]byte(lid)) txbkt, err := topbkt.CreateBucket([]byte(l.ID))
if err != nil { if err != nil {
if err == bolt.ErrBucketExists { if err == bolt.ErrBucketExists {
err = errdefs.ErrAlreadyExists err = errdefs.ErrAlreadyExists
} }
return Lease{}, errors.Wrapf(err, "lease %q", lid) return leases.Lease{}, errors.Wrapf(err, "lease %q", l.ID)
} }
t := time.Now().UTC() t := time.Now().UTC()
createdAt, err := t.MarshalBinary() createdAt, err := t.MarshalBinary()
if err != nil { if err != nil {
return Lease{}, err return leases.Lease{}, err
} }
if err := txbkt.Put(bucketKeyCreatedAt, createdAt); err != nil { if err := txbkt.Put(bucketKeyCreatedAt, createdAt); err != nil {
return Lease{}, err return leases.Lease{}, err
} }
if labels != nil { if l.Labels != nil {
if err := boltutil.WriteLabels(txbkt, labels); err != nil { if err := boltutil.WriteLabels(txbkt, l.Labels); err != nil {
return Lease{}, err return leases.Lease{}, err
} }
} }
l.CreatedAt = t
return Lease{ return l, nil
ID: lid,
CreatedAt: t,
Labels: labels,
}, nil
} }
// Delete delets the lease with the provided lease ID // Delete delets the lease with the provided lease ID
func (lm *LeaseManager) Delete(ctx context.Context, lid string) error { func (lm *LeaseManager) Delete(ctx context.Context, lease leases.Lease) error {
namespace, err := namespaces.NamespaceRequired(ctx) namespace, err := namespaces.NamespaceRequired(ctx)
if err != nil { if err != nil {
return err return err
@ -107,24 +103,24 @@ func (lm *LeaseManager) Delete(ctx context.Context, lid string) error {
if topbkt == nil { if topbkt == nil {
return nil return nil
} }
if err := topbkt.DeleteBucket([]byte(lid)); err != nil && err != bolt.ErrBucketNotFound { if err := topbkt.DeleteBucket([]byte(lease.ID)); err != nil && err != bolt.ErrBucketNotFound {
return err return err
} }
return nil return nil
} }
// List lists all active leases // List lists all active leases
func (lm *LeaseManager) List(ctx context.Context, includeResources bool, filter ...string) ([]Lease, error) { func (lm *LeaseManager) List(ctx context.Context, filter ...string) ([]leases.Lease, error) {
namespace, err := namespaces.NamespaceRequired(ctx) namespace, err := namespaces.NamespaceRequired(ctx)
if err != nil { if err != nil {
return nil, err return nil, err
} }
var leases []Lease var ll []leases.Lease
topbkt := getBucket(lm.tx, bucketKeyVersion, []byte(namespace), bucketKeyObjectLeases) topbkt := getBucket(lm.tx, bucketKeyVersion, []byte(namespace), bucketKeyObjectLeases)
if topbkt == nil { if topbkt == nil {
return leases, nil return ll, nil
} }
if err := topbkt.ForEach(func(k, v []byte) error { if err := topbkt.ForEach(func(k, v []byte) error {
@ -133,7 +129,7 @@ func (lm *LeaseManager) List(ctx context.Context, includeResources bool, filter
} }
txbkt := topbkt.Bucket(k) txbkt := topbkt.Bucket(k)
l := Lease{ l := leases.Lease{
ID: string(k), ID: string(k),
} }
@ -150,21 +146,18 @@ func (lm *LeaseManager) List(ctx context.Context, includeResources bool, filter
} }
l.Labels = labels l.Labels = labels
// TODO: Read Snapshots ll = append(ll, l)
// TODO: Read Content
leases = append(leases, l)
return nil return nil
}); err != nil { }); err != nil {
return nil, err return nil, err
} }
return leases, nil return ll, nil
} }
func addSnapshotLease(ctx context.Context, tx *bolt.Tx, snapshotter, key string) error { func addSnapshotLease(ctx context.Context, tx *bolt.Tx, snapshotter, key string) error {
lid, ok := leases.Lease(ctx) lid, ok := leases.FromContext(ctx)
if !ok { if !ok {
return nil return nil
} }
@ -193,7 +186,7 @@ func addSnapshotLease(ctx context.Context, tx *bolt.Tx, snapshotter, key string)
} }
func removeSnapshotLease(ctx context.Context, tx *bolt.Tx, snapshotter, key string) error { func removeSnapshotLease(ctx context.Context, tx *bolt.Tx, snapshotter, key string) error {
lid, ok := leases.Lease(ctx) lid, ok := leases.FromContext(ctx)
if !ok { if !ok {
return nil return nil
} }
@ -213,7 +206,7 @@ func removeSnapshotLease(ctx context.Context, tx *bolt.Tx, snapshotter, key stri
} }
func addContentLease(ctx context.Context, tx *bolt.Tx, dgst digest.Digest) error { func addContentLease(ctx context.Context, tx *bolt.Tx, dgst digest.Digest) error {
lid, ok := leases.Lease(ctx) lid, ok := leases.FromContext(ctx)
if !ok { if !ok {
return nil return nil
} }
@ -237,7 +230,7 @@ func addContentLease(ctx context.Context, tx *bolt.Tx, dgst digest.Digest) error
} }
func removeContentLease(ctx context.Context, tx *bolt.Tx, dgst digest.Digest) error { func removeContentLease(ctx context.Context, tx *bolt.Tx, dgst digest.Digest) error {
lid, ok := leases.Lease(ctx) lid, ok := leases.FromContext(ctx)
if !ok { if !ok {
return nil return nil
} }

View File

@ -21,6 +21,7 @@ import (
"github.com/boltdb/bolt" "github.com/boltdb/bolt"
"github.com/containerd/containerd/errdefs" "github.com/containerd/containerd/errdefs"
"github.com/containerd/containerd/leases"
"github.com/pkg/errors" "github.com/pkg/errors"
) )
@ -44,49 +45,51 @@ func TestLeases(t *testing.T) {
}, },
} }
var leases []Lease var ll []leases.Lease
for _, tc := range testCases { for _, tc := range testCases {
if err := db.Update(func(tx *bolt.Tx) error { if err := db.Update(func(tx *bolt.Tx) error {
lease, err := NewLeaseManager(tx).Create(ctx, tc.ID, nil) lease, err := NewLeaseManager(tx).Create(ctx, leases.WithID(tc.ID))
if err != nil { if err != nil {
if tc.Cause != nil && errors.Cause(err) == tc.Cause { if tc.Cause != nil && errors.Cause(err) == tc.Cause {
return nil return nil
} }
return err return err
} }
leases = append(leases, lease) ll = append(ll, lease)
return nil return nil
}); err != nil { }); err != nil {
t.Fatal(err) t.Fatal(err)
} }
} }
var listed []Lease var listed []leases.Lease
// List leases, check same // List leases, check same
if err := db.View(func(tx *bolt.Tx) error { if err := db.View(func(tx *bolt.Tx) error {
var err error var err error
listed, err = NewLeaseManager(tx).List(ctx, false) listed, err = NewLeaseManager(tx).List(ctx)
return err return err
}); err != nil { }); err != nil {
t.Fatal(err) t.Fatal(err)
} }
if len(listed) != len(leases) { if len(listed) != len(ll) {
t.Fatalf("Expected %d lease, got %d", len(leases), len(listed)) t.Fatalf("Expected %d lease, got %d", len(ll), len(listed))
} }
for i := range listed { for i := range listed {
if listed[i].ID != leases[i].ID { if listed[i].ID != ll[i].ID {
t.Fatalf("Expected lease ID %s, got %s", leases[i].ID, listed[i].ID) t.Fatalf("Expected lease ID %s, got %s", ll[i].ID, listed[i].ID)
} }
if listed[i].CreatedAt != leases[i].CreatedAt { if listed[i].CreatedAt != ll[i].CreatedAt {
t.Fatalf("Expected lease created at time %s, got %s", leases[i].CreatedAt, listed[i].CreatedAt) t.Fatalf("Expected lease created at time %s, got %s", ll[i].CreatedAt, listed[i].CreatedAt)
} }
} }
for _, tc := range testCases { for _, tc := range testCases {
if err := db.Update(func(tx *bolt.Tx) error { if err := db.Update(func(tx *bolt.Tx) error {
return NewLeaseManager(tx).Delete(ctx, tc.ID) return NewLeaseManager(tx).Delete(ctx, leases.Lease{
ID: tc.ID,
})
}); err != nil { }); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -94,7 +97,7 @@ func TestLeases(t *testing.T) {
if err := db.View(func(tx *bolt.Tx) error { if err := db.View(func(tx *bolt.Tx) error {
var err error var err error
listed, err = NewLeaseManager(tx).List(ctx, false) listed, err = NewLeaseManager(tx).List(ctx)
return err return err
}); err != nil { }); err != nil {
t.Fatal(err) t.Fatal(err)

View File

@ -25,10 +25,10 @@ import (
containers "github.com/containerd/containerd/api/services/containers/v1" containers "github.com/containerd/containerd/api/services/containers/v1"
diff "github.com/containerd/containerd/api/services/diff/v1" diff "github.com/containerd/containerd/api/services/diff/v1"
images "github.com/containerd/containerd/api/services/images/v1" images "github.com/containerd/containerd/api/services/images/v1"
leases "github.com/containerd/containerd/api/services/leases/v1"
namespacesapi "github.com/containerd/containerd/api/services/namespaces/v1" namespacesapi "github.com/containerd/containerd/api/services/namespaces/v1"
tasks "github.com/containerd/containerd/api/services/tasks/v1" tasks "github.com/containerd/containerd/api/services/tasks/v1"
"github.com/containerd/containerd/content" "github.com/containerd/containerd/content"
"github.com/containerd/containerd/leases"
"github.com/containerd/containerd/namespaces" "github.com/containerd/containerd/namespaces"
"github.com/containerd/containerd/plugin" "github.com/containerd/containerd/plugin"
"github.com/containerd/containerd/runtime/restart" "github.com/containerd/containerd/runtime/restart"
@ -120,7 +120,7 @@ func getServicesOpts(ic *plugin.InitContext) ([]containerd.ServicesOpt, error) {
return containerd.WithNamespaceService(s.(namespacesapi.NamespacesClient)) return containerd.WithNamespaceService(s.(namespacesapi.NamespacesClient))
}, },
services.LeasesService: func(s interface{}) containerd.ServicesOpt { services.LeasesService: func(s interface{}) containerd.ServicesOpt {
return containerd.WithLeasesService(s.(leases.LeasesClient)) return containerd.WithLeasesService(s.(leases.Manager))
}, },
} { } {
p := plugins[s] p := plugins[s]

View File

@ -20,12 +20,12 @@ import (
containersapi "github.com/containerd/containerd/api/services/containers/v1" containersapi "github.com/containerd/containerd/api/services/containers/v1"
"github.com/containerd/containerd/api/services/diff/v1" "github.com/containerd/containerd/api/services/diff/v1"
imagesapi "github.com/containerd/containerd/api/services/images/v1" imagesapi "github.com/containerd/containerd/api/services/images/v1"
"github.com/containerd/containerd/api/services/leases/v1"
namespacesapi "github.com/containerd/containerd/api/services/namespaces/v1" namespacesapi "github.com/containerd/containerd/api/services/namespaces/v1"
"github.com/containerd/containerd/api/services/tasks/v1" "github.com/containerd/containerd/api/services/tasks/v1"
"github.com/containerd/containerd/containers" "github.com/containerd/containerd/containers"
"github.com/containerd/containerd/content" "github.com/containerd/containerd/content"
"github.com/containerd/containerd/images" "github.com/containerd/containerd/images"
"github.com/containerd/containerd/leases"
"github.com/containerd/containerd/namespaces" "github.com/containerd/containerd/namespaces"
"github.com/containerd/containerd/snapshots" "github.com/containerd/containerd/snapshots"
) )
@ -39,7 +39,7 @@ type services struct {
taskService tasks.TasksClient taskService tasks.TasksClient
diffService DiffService diffService DiffService
eventService EventService eventService EventService
leasesService leases.LeasesClient leasesService leases.Manager
} }
// ServicesOpt allows callers to set options on the services // ServicesOpt allows callers to set options on the services
@ -105,7 +105,7 @@ func WithNamespaceService(namespaceService namespacesapi.NamespacesClient) Servi
} }
// WithLeasesService sets the lease service. // WithLeasesService sets the lease service.
func WithLeasesService(leasesService leases.LeasesClient) ServicesOpt { func WithLeasesService(leasesService leases.Manager) ServicesOpt {
return func(s *services) { return func(s *services) {
s.leasesService = leasesService s.leasesService = leasesService
} }

View File

@ -18,19 +18,12 @@ package leases
import ( import (
"context" "context"
"encoding/base64"
"fmt"
"math/rand"
"time"
"google.golang.org/grpc"
"github.com/boltdb/bolt" "github.com/boltdb/bolt"
api "github.com/containerd/containerd/api/services/leases/v1" "github.com/containerd/containerd/leases"
"github.com/containerd/containerd/metadata" "github.com/containerd/containerd/metadata"
"github.com/containerd/containerd/plugin" "github.com/containerd/containerd/plugin"
"github.com/containerd/containerd/services" "github.com/containerd/containerd/services"
ptypes "github.com/gogo/protobuf/types"
) )
func init() { func init() {
@ -54,67 +47,32 @@ type local struct {
db *metadata.DB db *metadata.DB
} }
func (l *local) Create(ctx context.Context, r *api.CreateRequest, _ ...grpc.CallOption) (*api.CreateResponse, error) { func (l *local) Create(ctx context.Context, opts ...leases.Opt) (leases.Lease, error) {
lid := r.ID var lease leases.Lease
if lid == "" {
lid = generateLeaseID()
}
var trans metadata.Lease
if err := l.db.Update(func(tx *bolt.Tx) error { if err := l.db.Update(func(tx *bolt.Tx) error {
var err error var err error
trans, err = metadata.NewLeaseManager(tx).Create(ctx, lid, r.Labels) lease, err = metadata.NewLeaseManager(tx).Create(ctx, opts...)
return err return err
}); err != nil { }); err != nil {
return nil, err return leases.Lease{}, err
} }
return &api.CreateResponse{ return lease, nil
Lease: txToGRPC(trans),
}, nil
} }
func (l *local) Delete(ctx context.Context, r *api.DeleteRequest, _ ...grpc.CallOption) (*ptypes.Empty, error) { func (l *local) Delete(ctx context.Context, lease leases.Lease) error {
if err := l.db.Update(func(tx *bolt.Tx) error { return l.db.Update(func(tx *bolt.Tx) error {
return metadata.NewLeaseManager(tx).Delete(ctx, r.ID) return metadata.NewLeaseManager(tx).Delete(ctx, lease)
}); err != nil { })
return nil, err
}
return &ptypes.Empty{}, nil
} }
func (l *local) List(ctx context.Context, r *api.ListRequest, _ ...grpc.CallOption) (*api.ListResponse, error) { func (l *local) List(ctx context.Context, filters ...string) ([]leases.Lease, error) {
var leases []metadata.Lease var ll []leases.Lease
if err := l.db.View(func(tx *bolt.Tx) error { if err := l.db.View(func(tx *bolt.Tx) error {
var err error var err error
leases, err = metadata.NewLeaseManager(tx).List(ctx, false, r.Filters...) ll, err = metadata.NewLeaseManager(tx).List(ctx, filters...)
return err return err
}); err != nil { }); err != nil {
return nil, err return nil, err
} }
return ll, nil
apileases := make([]*api.Lease, len(leases))
for i := range leases {
apileases[i] = txToGRPC(leases[i])
}
return &api.ListResponse{
Leases: apileases,
}, nil
}
func txToGRPC(tx metadata.Lease) *api.Lease {
return &api.Lease{
ID: tx.ID,
Labels: tx.Labels,
CreatedAt: tx.CreatedAt,
// TODO: Snapshots
// TODO: Content
}
}
func generateLeaseID() string {
t := time.Now()
var b [3]byte
// Ignore read failures, just decreases uniqueness
rand.Read(b[:])
return fmt.Sprintf("%d-%s", t.Nanosecond(), base64.URLEncoding.EncodeToString(b[:]))
} }

View File

@ -22,6 +22,7 @@ import (
"google.golang.org/grpc" "google.golang.org/grpc"
api "github.com/containerd/containerd/api/services/leases/v1" api "github.com/containerd/containerd/api/services/leases/v1"
"github.com/containerd/containerd/leases"
"github.com/containerd/containerd/plugin" "github.com/containerd/containerd/plugin"
"github.com/containerd/containerd/services" "github.com/containerd/containerd/services"
ptypes "github.com/gogo/protobuf/types" ptypes "github.com/gogo/protobuf/types"
@ -48,13 +49,13 @@ func init() {
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &service{local: i.(api.LeasesClient)}, nil return &service{lm: i.(leases.Manager)}, nil
}, },
}) })
} }
type service struct { type service struct {
local api.LeasesClient lm leases.Manager
} }
func (s *service) Register(server *grpc.Server) error { func (s *service) Register(server *grpc.Server) error {
@ -63,13 +64,54 @@ func (s *service) Register(server *grpc.Server) error {
} }
func (s *service) Create(ctx context.Context, r *api.CreateRequest) (*api.CreateResponse, error) { func (s *service) Create(ctx context.Context, r *api.CreateRequest) (*api.CreateResponse, error) {
return s.local.Create(ctx, r) opts := []leases.Opt{
leases.WithLabels(r.Labels),
}
if r.ID == "" {
opts = append(opts, leases.WithRandomID())
} else {
opts = append(opts, leases.WithID(r.ID))
}
l, err := s.lm.Create(ctx, opts...)
if err != nil {
return nil, err
}
return &api.CreateResponse{
Lease: leaseToGRPC(l),
}, nil
} }
func (s *service) Delete(ctx context.Context, r *api.DeleteRequest) (*ptypes.Empty, error) { func (s *service) Delete(ctx context.Context, r *api.DeleteRequest) (*ptypes.Empty, error) {
return s.local.Delete(ctx, r) if err := s.lm.Delete(ctx, leases.Lease{
ID: r.ID,
}); err != nil {
return nil, err
}
return &ptypes.Empty{}, nil
} }
func (s *service) List(ctx context.Context, r *api.ListRequest) (*api.ListResponse, error) { func (s *service) List(ctx context.Context, r *api.ListRequest) (*api.ListResponse, error) {
return s.local.List(ctx, r) l, err := s.lm.List(ctx, r.Filters...)
if err != nil {
return nil, err
}
apileases := make([]*api.Lease, len(l))
for i := range l {
apileases[i] = leaseToGRPC(l[i])
}
return &api.ListResponse{
Leases: apileases,
}, nil
}
func leaseToGRPC(l leases.Lease) *api.Lease {
return &api.Lease{
ID: l.ID,
Labels: l.Labels,
CreatedAt: l.CreatedAt,
}
} }