From e13894bb7ad766746d79c6063a069bddfccdb65b Mon Sep 17 00:00:00 2001 From: Derek McGowan Date: Thu, 26 Oct 2017 16:16:14 -0700 Subject: [PATCH] Add leases api Signed-off-by: Derek McGowan --- api/next.pb.txt | 176 +++ api/services/leases/v1/doc.go | 1 + api/services/leases/v1/leases.pb.go | 1749 +++++++++++++++++++++++++++ api/services/leases/v1/leases.proto | 64 + client.go | 26 + cmd/containerd/builtins.go | 1 + lease.go | 71 ++ leases/context.go | 24 + leases/grpc.go | 41 + metadata/buckets.go | 2 + metadata/content.go | 30 +- metadata/gc.go | 49 + metadata/gc_test.go | 28 + metadata/leases.go | 201 +++ metadata/leases_test.go | 90 ++ metadata/snapshot.go | 4 + server/server.go | 3 + services/leases/service.go | 115 ++ task.go | 9 + 19 files changed, 2671 insertions(+), 13 deletions(-) create mode 100644 api/services/leases/v1/doc.go create mode 100644 api/services/leases/v1/leases.pb.go create mode 100644 api/services/leases/v1/leases.proto create mode 100644 lease.go create mode 100644 leases/context.go create mode 100644 leases/grpc.go create mode 100644 metadata/leases.go create mode 100644 metadata/leases_test.go create mode 100644 services/leases/service.go diff --git a/api/next.pb.txt b/api/next.pb.txt index c58ef4d08..2ee172559 100755 --- a/api/next.pb.txt +++ b/api/next.pb.txt @@ -2405,6 +2405,182 @@ file { } syntax: "proto3" } +file { + name: "github.com/containerd/containerd/api/services/leases/v1/leases.proto" + package: "containerd.services.leases.v1" + dependency: "gogoproto/gogo.proto" + dependency: "google/protobuf/empty.proto" + dependency: "google/protobuf/timestamp.proto" + message_type { + name: "Snapshot" + field { + name: "snapshotter" + number: 1 + label: LABEL_OPTIONAL + type: TYPE_STRING + json_name: "snapshotter" + } + field { + name: "key" + number: 2 + label: LABEL_OPTIONAL + type: TYPE_STRING + json_name: "key" + } + } + message_type { + name: "Lease" + field { + name: "id" + number: 1 + label: LABEL_OPTIONAL + type: TYPE_STRING + json_name: "id" + } + field { + name: "created_at" + number: 2 + label: LABEL_OPTIONAL + type: TYPE_MESSAGE + type_name: ".google.protobuf.Timestamp" + options { + 65010: 1 + 65001: 0 + } + json_name: "createdAt" + } + field { + name: "labels" + number: 3 + label: LABEL_REPEATED + type: TYPE_MESSAGE + type_name: ".containerd.services.leases.v1.Lease.LabelsEntry" + json_name: "labels" + } + nested_type { + name: "LabelsEntry" + field { + name: "key" + number: 1 + label: LABEL_OPTIONAL + type: TYPE_STRING + json_name: "key" + } + field { + name: "value" + number: 2 + label: LABEL_OPTIONAL + type: TYPE_STRING + json_name: "value" + } + options { + map_entry: true + } + } + } + message_type { + name: "CreateRequest" + field { + name: "id" + number: 1 + label: LABEL_OPTIONAL + type: TYPE_STRING + json_name: "id" + } + field { + name: "labels" + number: 3 + label: LABEL_REPEATED + type: TYPE_MESSAGE + type_name: ".containerd.services.leases.v1.CreateRequest.LabelsEntry" + json_name: "labels" + } + nested_type { + name: "LabelsEntry" + field { + name: "key" + number: 1 + label: LABEL_OPTIONAL + type: TYPE_STRING + json_name: "key" + } + field { + name: "value" + number: 2 + label: LABEL_OPTIONAL + type: TYPE_STRING + json_name: "value" + } + options { + map_entry: true + } + } + } + message_type { + name: "CreateResponse" + field { + name: "lease" + number: 1 + label: LABEL_OPTIONAL + type: TYPE_MESSAGE + type_name: ".containerd.services.leases.v1.Lease" + json_name: "lease" + } + } + message_type { + name: "DeleteRequest" + field { + name: "id" + number: 1 + label: LABEL_OPTIONAL + type: TYPE_STRING + json_name: "id" + } + } + message_type { + name: "ListRequest" + field { + name: "filters" + number: 1 + label: LABEL_REPEATED + type: TYPE_STRING + json_name: "filters" + } + } + message_type { + name: "ListResponse" + field { + name: "leases" + number: 1 + label: LABEL_REPEATED + type: TYPE_MESSAGE + type_name: ".containerd.services.leases.v1.Lease" + json_name: "leases" + } + } + service { + name: "Leases" + method { + name: "Create" + input_type: ".containerd.services.leases.v1.CreateRequest" + output_type: ".containerd.services.leases.v1.CreateResponse" + } + method { + name: "Delete" + input_type: ".containerd.services.leases.v1.DeleteRequest" + output_type: ".google.protobuf.Empty" + } + method { + name: "List" + input_type: ".containerd.services.leases.v1.ListRequest" + output_type: ".containerd.services.leases.v1.ListResponse" + } + } + options { + go_package: "github.com/containerd/containerd/api/services/leases/v1;leases" + } + syntax: "proto3" +} file { name: "github.com/containerd/containerd/api/services/namespaces/v1/namespace.proto" package: "containerd.services.namespaces.v1" diff --git a/api/services/leases/v1/doc.go b/api/services/leases/v1/doc.go new file mode 100644 index 000000000..3685b6455 --- /dev/null +++ b/api/services/leases/v1/doc.go @@ -0,0 +1 @@ +package leases diff --git a/api/services/leases/v1/leases.pb.go b/api/services/leases/v1/leases.pb.go new file mode 100644 index 000000000..e2e18dfff --- /dev/null +++ b/api/services/leases/v1/leases.pb.go @@ -0,0 +1,1749 @@ +// Code generated by protoc-gen-gogo. +// source: github.com/containerd/containerd/api/services/leases/v1/leases.proto +// DO NOT EDIT! + +/* + Package leases is a generated protocol buffer package. + + It is generated from these files: + github.com/containerd/containerd/api/services/leases/v1/leases.proto + + It has these top-level messages: + Snapshot + Lease + CreateRequest + CreateResponse + DeleteRequest + ListRequest + ListResponse +*/ +package leases + +import proto "github.com/gogo/protobuf/proto" +import fmt "fmt" +import math "math" +import _ "github.com/gogo/protobuf/gogoproto" +import google_protobuf1 "github.com/golang/protobuf/ptypes/empty" +import _ "github.com/gogo/protobuf/types" + +import time "time" + +import ( + context "golang.org/x/net/context" + grpc "google.golang.org/grpc" +) + +import github_com_gogo_protobuf_types "github.com/gogo/protobuf/types" + +import strings "strings" +import reflect "reflect" +import github_com_gogo_protobuf_sortkeys "github.com/gogo/protobuf/sortkeys" + +import io "io" + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf +var _ = time.Kitchen + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion2 // please upgrade the proto package + +// Snapshot is a snapshot resource reference. +type Snapshot struct { + Snapshotter string `protobuf:"bytes,1,opt,name=snapshotter,proto3" json:"snapshotter,omitempty"` + Key string `protobuf:"bytes,2,opt,name=key,proto3" json:"key,omitempty"` +} + +func (m *Snapshot) Reset() { *m = Snapshot{} } +func (*Snapshot) ProtoMessage() {} +func (*Snapshot) Descriptor() ([]byte, []int) { return fileDescriptorLeases, []int{0} } + +// Lease is an object which retains resources while it exists. +type Lease struct { + ID string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + CreatedAt time.Time `protobuf:"bytes,2,opt,name=created_at,json=createdAt,stdtime" json:"created_at"` + Labels map[string]string `protobuf:"bytes,3,rep,name=labels" json:"labels,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` +} + +func (m *Lease) Reset() { *m = Lease{} } +func (*Lease) ProtoMessage() {} +func (*Lease) Descriptor() ([]byte, []int) { return fileDescriptorLeases, []int{1} } + +type CreateRequest struct { + // ID is used to identity the lease, when the id is not set the service + // generates a random identifier for the lease. + ID string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Labels map[string]string `protobuf:"bytes,3,rep,name=labels" json:"labels,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` +} + +func (m *CreateRequest) Reset() { *m = CreateRequest{} } +func (*CreateRequest) ProtoMessage() {} +func (*CreateRequest) Descriptor() ([]byte, []int) { return fileDescriptorLeases, []int{2} } + +type CreateResponse struct { + Lease *Lease `protobuf:"bytes,1,opt,name=lease" json:"lease,omitempty"` +} + +func (m *CreateResponse) Reset() { *m = CreateResponse{} } +func (*CreateResponse) ProtoMessage() {} +func (*CreateResponse) Descriptor() ([]byte, []int) { return fileDescriptorLeases, []int{3} } + +type DeleteRequest struct { + ID string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` +} + +func (m *DeleteRequest) Reset() { *m = DeleteRequest{} } +func (*DeleteRequest) ProtoMessage() {} +func (*DeleteRequest) Descriptor() ([]byte, []int) { return fileDescriptorLeases, []int{4} } + +type ListRequest struct { + Filters []string `protobuf:"bytes,1,rep,name=filters" json:"filters,omitempty"` +} + +func (m *ListRequest) Reset() { *m = ListRequest{} } +func (*ListRequest) ProtoMessage() {} +func (*ListRequest) Descriptor() ([]byte, []int) { return fileDescriptorLeases, []int{5} } + +type ListResponse struct { + Leases []*Lease `protobuf:"bytes,1,rep,name=leases" json:"leases,omitempty"` +} + +func (m *ListResponse) Reset() { *m = ListResponse{} } +func (*ListResponse) ProtoMessage() {} +func (*ListResponse) Descriptor() ([]byte, []int) { return fileDescriptorLeases, []int{6} } + +func init() { + proto.RegisterType((*Snapshot)(nil), "containerd.services.leases.v1.Snapshot") + proto.RegisterType((*Lease)(nil), "containerd.services.leases.v1.Lease") + proto.RegisterType((*CreateRequest)(nil), "containerd.services.leases.v1.CreateRequest") + proto.RegisterType((*CreateResponse)(nil), "containerd.services.leases.v1.CreateResponse") + proto.RegisterType((*DeleteRequest)(nil), "containerd.services.leases.v1.DeleteRequest") + proto.RegisterType((*ListRequest)(nil), "containerd.services.leases.v1.ListRequest") + proto.RegisterType((*ListResponse)(nil), "containerd.services.leases.v1.ListResponse") +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion4 + +// Client API for Leases service + +type LeasesClient interface { + // Create creates a new lease for managing changes to metadata. A lease + // can be used to protect objects from being removed. + Create(ctx context.Context, in *CreateRequest, opts ...grpc.CallOption) (*CreateResponse, error) + // Delete deletes the lease and makes any unreferenced objects created + // during the lease eligible for garbage collection if not referenced + // or retained by other resources during the lease. + Delete(ctx context.Context, in *DeleteRequest, opts ...grpc.CallOption) (*google_protobuf1.Empty, error) + // ListTransactions lists all active leases, returning the full list of + // leases and optionally including the referenced resources. + List(ctx context.Context, in *ListRequest, opts ...grpc.CallOption) (*ListResponse, error) +} + +type leasesClient struct { + cc *grpc.ClientConn +} + +func NewLeasesClient(cc *grpc.ClientConn) LeasesClient { + return &leasesClient{cc} +} + +func (c *leasesClient) Create(ctx context.Context, in *CreateRequest, opts ...grpc.CallOption) (*CreateResponse, error) { + out := new(CreateResponse) + err := grpc.Invoke(ctx, "/containerd.services.leases.v1.Leases/Create", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *leasesClient) Delete(ctx context.Context, in *DeleteRequest, opts ...grpc.CallOption) (*google_protobuf1.Empty, error) { + out := new(google_protobuf1.Empty) + err := grpc.Invoke(ctx, "/containerd.services.leases.v1.Leases/Delete", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *leasesClient) List(ctx context.Context, in *ListRequest, opts ...grpc.CallOption) (*ListResponse, error) { + out := new(ListResponse) + err := grpc.Invoke(ctx, "/containerd.services.leases.v1.Leases/List", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// Server API for Leases service + +type LeasesServer interface { + // Create creates a new lease for managing changes to metadata. A lease + // can be used to protect objects from being removed. + Create(context.Context, *CreateRequest) (*CreateResponse, error) + // Delete deletes the lease and makes any unreferenced objects created + // during the lease eligible for garbage collection if not referenced + // or retained by other resources during the lease. + Delete(context.Context, *DeleteRequest) (*google_protobuf1.Empty, error) + // ListTransactions lists all active leases, returning the full list of + // leases and optionally including the referenced resources. + List(context.Context, *ListRequest) (*ListResponse, error) +} + +func RegisterLeasesServer(s *grpc.Server, srv LeasesServer) { + s.RegisterService(&_Leases_serviceDesc, srv) +} + +func _Leases_Create_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CreateRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(LeasesServer).Create(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/containerd.services.leases.v1.Leases/Create", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(LeasesServer).Create(ctx, req.(*CreateRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Leases_Delete_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DeleteRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(LeasesServer).Delete(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/containerd.services.leases.v1.Leases/Delete", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(LeasesServer).Delete(ctx, req.(*DeleteRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Leases_List_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(LeasesServer).List(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/containerd.services.leases.v1.Leases/List", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(LeasesServer).List(ctx, req.(*ListRequest)) + } + return interceptor(ctx, in, info, handler) +} + +var _Leases_serviceDesc = grpc.ServiceDesc{ + ServiceName: "containerd.services.leases.v1.Leases", + HandlerType: (*LeasesServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Create", + Handler: _Leases_Create_Handler, + }, + { + MethodName: "Delete", + Handler: _Leases_Delete_Handler, + }, + { + MethodName: "List", + Handler: _Leases_List_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "github.com/containerd/containerd/api/services/leases/v1/leases.proto", +} + +func (m *Snapshot) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Snapshot) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.Snapshotter) > 0 { + dAtA[i] = 0xa + i++ + i = encodeVarintLeases(dAtA, i, uint64(len(m.Snapshotter))) + i += copy(dAtA[i:], m.Snapshotter) + } + if len(m.Key) > 0 { + dAtA[i] = 0x12 + i++ + i = encodeVarintLeases(dAtA, i, uint64(len(m.Key))) + i += copy(dAtA[i:], m.Key) + } + return i, nil +} + +func (m *Lease) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Lease) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.ID) > 0 { + dAtA[i] = 0xa + i++ + i = encodeVarintLeases(dAtA, i, uint64(len(m.ID))) + i += copy(dAtA[i:], m.ID) + } + dAtA[i] = 0x12 + i++ + i = encodeVarintLeases(dAtA, i, uint64(github_com_gogo_protobuf_types.SizeOfStdTime(m.CreatedAt))) + n1, err := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.CreatedAt, dAtA[i:]) + if err != nil { + return 0, err + } + i += n1 + if len(m.Labels) > 0 { + for k, _ := range m.Labels { + dAtA[i] = 0x1a + i++ + v := m.Labels[k] + mapSize := 1 + len(k) + sovLeases(uint64(len(k))) + 1 + len(v) + sovLeases(uint64(len(v))) + i = encodeVarintLeases(dAtA, i, uint64(mapSize)) + dAtA[i] = 0xa + i++ + i = encodeVarintLeases(dAtA, i, uint64(len(k))) + i += copy(dAtA[i:], k) + dAtA[i] = 0x12 + i++ + i = encodeVarintLeases(dAtA, i, uint64(len(v))) + i += copy(dAtA[i:], v) + } + } + return i, nil +} + +func (m *CreateRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *CreateRequest) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.ID) > 0 { + dAtA[i] = 0xa + i++ + i = encodeVarintLeases(dAtA, i, uint64(len(m.ID))) + i += copy(dAtA[i:], m.ID) + } + if len(m.Labels) > 0 { + for k, _ := range m.Labels { + dAtA[i] = 0x1a + i++ + v := m.Labels[k] + mapSize := 1 + len(k) + sovLeases(uint64(len(k))) + 1 + len(v) + sovLeases(uint64(len(v))) + i = encodeVarintLeases(dAtA, i, uint64(mapSize)) + dAtA[i] = 0xa + i++ + i = encodeVarintLeases(dAtA, i, uint64(len(k))) + i += copy(dAtA[i:], k) + dAtA[i] = 0x12 + i++ + i = encodeVarintLeases(dAtA, i, uint64(len(v))) + i += copy(dAtA[i:], v) + } + } + return i, nil +} + +func (m *CreateResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *CreateResponse) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.Lease != nil { + dAtA[i] = 0xa + i++ + i = encodeVarintLeases(dAtA, i, uint64(m.Lease.Size())) + n2, err := m.Lease.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n2 + } + return i, nil +} + +func (m *DeleteRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *DeleteRequest) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.ID) > 0 { + dAtA[i] = 0xa + i++ + i = encodeVarintLeases(dAtA, i, uint64(len(m.ID))) + i += copy(dAtA[i:], m.ID) + } + return i, nil +} + +func (m *ListRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ListRequest) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.Filters) > 0 { + for _, s := range m.Filters { + dAtA[i] = 0xa + i++ + l = len(s) + for l >= 1<<7 { + dAtA[i] = uint8(uint64(l)&0x7f | 0x80) + l >>= 7 + i++ + } + dAtA[i] = uint8(l) + i++ + i += copy(dAtA[i:], s) + } + } + return i, nil +} + +func (m *ListResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ListResponse) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.Leases) > 0 { + for _, msg := range m.Leases { + dAtA[i] = 0xa + i++ + i = encodeVarintLeases(dAtA, i, uint64(msg.Size())) + n, err := msg.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n + } + } + return i, nil +} + +func encodeFixed64Leases(dAtA []byte, offset int, v uint64) int { + dAtA[offset] = uint8(v) + dAtA[offset+1] = uint8(v >> 8) + dAtA[offset+2] = uint8(v >> 16) + dAtA[offset+3] = uint8(v >> 24) + dAtA[offset+4] = uint8(v >> 32) + dAtA[offset+5] = uint8(v >> 40) + dAtA[offset+6] = uint8(v >> 48) + dAtA[offset+7] = uint8(v >> 56) + return offset + 8 +} +func encodeFixed32Leases(dAtA []byte, offset int, v uint32) int { + dAtA[offset] = uint8(v) + dAtA[offset+1] = uint8(v >> 8) + dAtA[offset+2] = uint8(v >> 16) + dAtA[offset+3] = uint8(v >> 24) + return offset + 4 +} +func encodeVarintLeases(dAtA []byte, offset int, v uint64) int { + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return offset + 1 +} +func (m *Snapshot) Size() (n int) { + var l int + _ = l + l = len(m.Snapshotter) + if l > 0 { + n += 1 + l + sovLeases(uint64(l)) + } + l = len(m.Key) + if l > 0 { + n += 1 + l + sovLeases(uint64(l)) + } + return n +} + +func (m *Lease) Size() (n int) { + var l int + _ = l + l = len(m.ID) + if l > 0 { + n += 1 + l + sovLeases(uint64(l)) + } + l = github_com_gogo_protobuf_types.SizeOfStdTime(m.CreatedAt) + n += 1 + l + sovLeases(uint64(l)) + if len(m.Labels) > 0 { + for k, v := range m.Labels { + _ = k + _ = v + mapEntrySize := 1 + len(k) + sovLeases(uint64(len(k))) + 1 + len(v) + sovLeases(uint64(len(v))) + n += mapEntrySize + 1 + sovLeases(uint64(mapEntrySize)) + } + } + return n +} + +func (m *CreateRequest) Size() (n int) { + var l int + _ = l + l = len(m.ID) + if l > 0 { + n += 1 + l + sovLeases(uint64(l)) + } + if len(m.Labels) > 0 { + for k, v := range m.Labels { + _ = k + _ = v + mapEntrySize := 1 + len(k) + sovLeases(uint64(len(k))) + 1 + len(v) + sovLeases(uint64(len(v))) + n += mapEntrySize + 1 + sovLeases(uint64(mapEntrySize)) + } + } + return n +} + +func (m *CreateResponse) Size() (n int) { + var l int + _ = l + if m.Lease != nil { + l = m.Lease.Size() + n += 1 + l + sovLeases(uint64(l)) + } + return n +} + +func (m *DeleteRequest) Size() (n int) { + var l int + _ = l + l = len(m.ID) + if l > 0 { + n += 1 + l + sovLeases(uint64(l)) + } + return n +} + +func (m *ListRequest) Size() (n int) { + var l int + _ = l + if len(m.Filters) > 0 { + for _, s := range m.Filters { + l = len(s) + n += 1 + l + sovLeases(uint64(l)) + } + } + return n +} + +func (m *ListResponse) Size() (n int) { + var l int + _ = l + if len(m.Leases) > 0 { + for _, e := range m.Leases { + l = e.Size() + n += 1 + l + sovLeases(uint64(l)) + } + } + return n +} + +func sovLeases(x uint64) (n int) { + for { + n++ + x >>= 7 + if x == 0 { + break + } + } + return n +} +func sozLeases(x uint64) (n int) { + return sovLeases(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (this *Snapshot) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&Snapshot{`, + `Snapshotter:` + fmt.Sprintf("%v", this.Snapshotter) + `,`, + `Key:` + fmt.Sprintf("%v", this.Key) + `,`, + `}`, + }, "") + return s +} +func (this *Lease) String() string { + if this == nil { + return "nil" + } + keysForLabels := make([]string, 0, len(this.Labels)) + for k, _ := range this.Labels { + keysForLabels = append(keysForLabels, k) + } + github_com_gogo_protobuf_sortkeys.Strings(keysForLabels) + mapStringForLabels := "map[string]string{" + for _, k := range keysForLabels { + mapStringForLabels += fmt.Sprintf("%v: %v,", k, this.Labels[k]) + } + mapStringForLabels += "}" + s := strings.Join([]string{`&Lease{`, + `ID:` + fmt.Sprintf("%v", this.ID) + `,`, + `CreatedAt:` + strings.Replace(strings.Replace(this.CreatedAt.String(), "Timestamp", "google_protobuf2.Timestamp", 1), `&`, ``, 1) + `,`, + `Labels:` + mapStringForLabels + `,`, + `}`, + }, "") + return s +} +func (this *CreateRequest) String() string { + if this == nil { + return "nil" + } + keysForLabels := make([]string, 0, len(this.Labels)) + for k, _ := range this.Labels { + keysForLabels = append(keysForLabels, k) + } + github_com_gogo_protobuf_sortkeys.Strings(keysForLabels) + mapStringForLabels := "map[string]string{" + for _, k := range keysForLabels { + mapStringForLabels += fmt.Sprintf("%v: %v,", k, this.Labels[k]) + } + mapStringForLabels += "}" + s := strings.Join([]string{`&CreateRequest{`, + `ID:` + fmt.Sprintf("%v", this.ID) + `,`, + `Labels:` + mapStringForLabels + `,`, + `}`, + }, "") + return s +} +func (this *CreateResponse) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&CreateResponse{`, + `Lease:` + strings.Replace(fmt.Sprintf("%v", this.Lease), "Lease", "Lease", 1) + `,`, + `}`, + }, "") + return s +} +func (this *DeleteRequest) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&DeleteRequest{`, + `ID:` + fmt.Sprintf("%v", this.ID) + `,`, + `}`, + }, "") + return s +} +func (this *ListRequest) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&ListRequest{`, + `Filters:` + fmt.Sprintf("%v", this.Filters) + `,`, + `}`, + }, "") + return s +} +func (this *ListResponse) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&ListResponse{`, + `Leases:` + strings.Replace(fmt.Sprintf("%v", this.Leases), "Lease", "Lease", 1) + `,`, + `}`, + }, "") + return s +} +func valueToStringLeases(v interface{}) string { + rv := reflect.ValueOf(v) + if rv.IsNil() { + return "nil" + } + pv := reflect.Indirect(rv).Interface() + return fmt.Sprintf("*%v", pv) +} +func (m *Snapshot) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLeases + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Snapshot: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Snapshot: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Snapshotter", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLeases + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthLeases + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Snapshotter = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Key", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLeases + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthLeases + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Key = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipLeases(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthLeases + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *Lease) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLeases + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Lease: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Lease: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ID", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLeases + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthLeases + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ID = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field CreatedAt", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLeases + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthLeases + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := github_com_gogo_protobuf_types.StdTimeUnmarshal(&m.CreatedAt, dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Labels", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLeases + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthLeases + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + var keykey uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLeases + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + keykey |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + var stringLenmapkey uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLeases + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLenmapkey |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLenmapkey := int(stringLenmapkey) + if intStringLenmapkey < 0 { + return ErrInvalidLengthLeases + } + postStringIndexmapkey := iNdEx + intStringLenmapkey + if postStringIndexmapkey > l { + return io.ErrUnexpectedEOF + } + mapkey := string(dAtA[iNdEx:postStringIndexmapkey]) + iNdEx = postStringIndexmapkey + if m.Labels == nil { + m.Labels = make(map[string]string) + } + if iNdEx < postIndex { + var valuekey uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLeases + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + valuekey |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + var stringLenmapvalue uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLeases + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLenmapvalue |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLenmapvalue := int(stringLenmapvalue) + if intStringLenmapvalue < 0 { + return ErrInvalidLengthLeases + } + postStringIndexmapvalue := iNdEx + intStringLenmapvalue + if postStringIndexmapvalue > l { + return io.ErrUnexpectedEOF + } + mapvalue := string(dAtA[iNdEx:postStringIndexmapvalue]) + iNdEx = postStringIndexmapvalue + m.Labels[mapkey] = mapvalue + } else { + var mapvalue string + m.Labels[mapkey] = mapvalue + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipLeases(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthLeases + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *CreateRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLeases + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: CreateRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: CreateRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ID", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLeases + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthLeases + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ID = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Labels", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLeases + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthLeases + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + var keykey uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLeases + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + keykey |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + var stringLenmapkey uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLeases + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLenmapkey |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLenmapkey := int(stringLenmapkey) + if intStringLenmapkey < 0 { + return ErrInvalidLengthLeases + } + postStringIndexmapkey := iNdEx + intStringLenmapkey + if postStringIndexmapkey > l { + return io.ErrUnexpectedEOF + } + mapkey := string(dAtA[iNdEx:postStringIndexmapkey]) + iNdEx = postStringIndexmapkey + if m.Labels == nil { + m.Labels = make(map[string]string) + } + if iNdEx < postIndex { + var valuekey uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLeases + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + valuekey |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + var stringLenmapvalue uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLeases + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLenmapvalue |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLenmapvalue := int(stringLenmapvalue) + if intStringLenmapvalue < 0 { + return ErrInvalidLengthLeases + } + postStringIndexmapvalue := iNdEx + intStringLenmapvalue + if postStringIndexmapvalue > l { + return io.ErrUnexpectedEOF + } + mapvalue := string(dAtA[iNdEx:postStringIndexmapvalue]) + iNdEx = postStringIndexmapvalue + m.Labels[mapkey] = mapvalue + } else { + var mapvalue string + m.Labels[mapkey] = mapvalue + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipLeases(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthLeases + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *CreateResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLeases + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: CreateResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: CreateResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Lease", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLeases + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthLeases + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Lease == nil { + m.Lease = &Lease{} + } + if err := m.Lease.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipLeases(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthLeases + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *DeleteRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLeases + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: DeleteRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: DeleteRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ID", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLeases + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthLeases + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ID = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipLeases(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthLeases + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *ListRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLeases + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ListRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ListRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Filters", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLeases + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthLeases + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Filters = append(m.Filters, string(dAtA[iNdEx:postIndex])) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipLeases(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthLeases + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *ListResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLeases + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ListResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ListResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Leases", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLeases + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthLeases + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Leases = append(m.Leases, &Lease{}) + if err := m.Leases[len(m.Leases)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipLeases(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthLeases + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipLeases(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowLeases + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowLeases + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + return iNdEx, nil + case 1: + iNdEx += 8 + return iNdEx, nil + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowLeases + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + iNdEx += length + if length < 0 { + return 0, ErrInvalidLengthLeases + } + return iNdEx, nil + case 3: + for { + var innerWire uint64 + var start int = iNdEx + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowLeases + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + innerWire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + innerWireType := int(innerWire & 0x7) + if innerWireType == 4 { + break + } + next, err := skipLeases(dAtA[start:]) + if err != nil { + return 0, err + } + iNdEx = start + next + } + return iNdEx, nil + case 4: + return iNdEx, nil + case 5: + iNdEx += 4 + return iNdEx, nil + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + } + panic("unreachable") +} + +var ( + ErrInvalidLengthLeases = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowLeases = fmt.Errorf("proto: integer overflow") +) + +func init() { + proto.RegisterFile("github.com/containerd/containerd/api/services/leases/v1/leases.proto", fileDescriptorLeases) +} + +var fileDescriptorLeases = []byte{ + // 525 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x94, 0x4f, 0x6b, 0x13, 0x41, + 0x18, 0xc6, 0x33, 0x1b, 0xb3, 0x36, 0xef, 0x5a, 0x91, 0xa1, 0x94, 0xb0, 0xe2, 0x26, 0x2c, 0x42, + 0x83, 0x7f, 0x66, 0x6d, 0xbc, 0xd4, 0x2a, 0x05, 0xd3, 0x14, 0x14, 0x82, 0xc8, 0xea, 0x41, 0xbc, + 0x94, 0x4d, 0xf2, 0x36, 0x5d, 0xdc, 0x64, 0xd6, 0x9d, 0x49, 0x20, 0x37, 0x3f, 0x82, 0x1f, 0xc1, + 0x0f, 0xe1, 0x87, 0xc8, 0xd1, 0xa3, 0xa7, 0x6a, 0x73, 0xf3, 0x5b, 0x48, 0x66, 0x66, 0x6d, 0x5a, + 0xd1, 0x44, 0xe9, 0xed, 0x9d, 0x99, 0xe7, 0xd9, 0xf9, 0x3d, 0x0f, 0xcc, 0x42, 0xab, 0x1f, 0xcb, + 0xe3, 0x51, 0x87, 0x75, 0xf9, 0x20, 0xe8, 0xf2, 0xa1, 0x8c, 0xe2, 0x21, 0x66, 0xbd, 0xc5, 0x31, + 0x4a, 0xe3, 0x40, 0x60, 0x36, 0x8e, 0xbb, 0x28, 0x82, 0x04, 0x23, 0x81, 0x22, 0x18, 0x6f, 0x9b, + 0x89, 0xa5, 0x19, 0x97, 0x9c, 0xde, 0x3a, 0xd3, 0xb3, 0x5c, 0xcb, 0x8c, 0x62, 0xbc, 0xed, 0x6e, + 0xf4, 0x79, 0x9f, 0x2b, 0x65, 0x30, 0x9f, 0xb4, 0xc9, 0xbd, 0xd9, 0xe7, 0xbc, 0x9f, 0x60, 0xa0, + 0x56, 0x9d, 0xd1, 0x51, 0x80, 0x83, 0x54, 0x4e, 0xcc, 0x61, 0xf5, 0xe2, 0xa1, 0x8c, 0x07, 0x28, + 0x64, 0x34, 0x48, 0xb5, 0xc0, 0xdf, 0x83, 0xb5, 0x57, 0xc3, 0x28, 0x15, 0xc7, 0x5c, 0xd2, 0x1a, + 0x38, 0xc2, 0xcc, 0x12, 0xb3, 0x0a, 0xa9, 0x91, 0x7a, 0x39, 0x5c, 0xdc, 0xa2, 0x37, 0xa0, 0xf8, + 0x0e, 0x27, 0x15, 0x4b, 0x9d, 0xcc, 0x47, 0xff, 0x07, 0x81, 0x52, 0x7b, 0x4e, 0x48, 0x37, 0xc1, + 0x8a, 0x7b, 0xda, 0xd4, 0xb4, 0x67, 0x27, 0x55, 0xeb, 0x79, 0x2b, 0xb4, 0xe2, 0x1e, 0xdd, 0x07, + 0xe8, 0x66, 0x18, 0x49, 0xec, 0x1d, 0x46, 0x52, 0x59, 0x9d, 0x86, 0xcb, 0x34, 0x17, 0xcb, 0xb9, + 0xd8, 0xeb, 0x9c, 0xab, 0xb9, 0x36, 0x3d, 0xa9, 0x16, 0x3e, 0x7e, 0xab, 0x92, 0xb0, 0x6c, 0x7c, + 0x4f, 0x25, 0x7d, 0x06, 0x76, 0x12, 0x75, 0x30, 0x11, 0x95, 0x62, 0xad, 0x58, 0x77, 0x1a, 0x0f, + 0xd8, 0x5f, 0xab, 0x62, 0x0a, 0x89, 0xb5, 0x95, 0xe5, 0x60, 0x28, 0xb3, 0x49, 0x68, 0xfc, 0xee, + 0x23, 0x70, 0x16, 0xb6, 0xf3, 0x44, 0xe4, 0x57, 0x22, 0xba, 0x01, 0xa5, 0x71, 0x94, 0x8c, 0xd0, + 0xa4, 0xd4, 0x8b, 0x5d, 0x6b, 0x87, 0xf8, 0x9f, 0x09, 0xac, 0xef, 0x2b, 0xa4, 0x10, 0xdf, 0x8f, + 0x50, 0xc8, 0x3f, 0x66, 0x7e, 0x79, 0x01, 0x77, 0x67, 0x09, 0xee, 0xb9, 0xaf, 0x5e, 0x36, 0x76, + 0x1b, 0xae, 0xe7, 0xdf, 0x17, 0x29, 0x1f, 0x0a, 0xa4, 0xbb, 0x50, 0x52, 0x77, 0x2b, 0xbf, 0xd3, + 0xb8, 0xbd, 0x4a, 0x99, 0xa1, 0xb6, 0xf8, 0x5b, 0xb0, 0xde, 0xc2, 0x04, 0x97, 0x76, 0xe0, 0x6f, + 0x81, 0xd3, 0x8e, 0x85, 0xcc, 0x65, 0x15, 0xb8, 0x7a, 0x14, 0x27, 0x12, 0x33, 0x51, 0x21, 0xb5, + 0x62, 0xbd, 0x1c, 0xe6, 0x4b, 0xbf, 0x0d, 0xd7, 0xb4, 0xd0, 0xd0, 0x3d, 0x01, 0x5b, 0xdf, 0xad, + 0x84, 0xab, 0xe2, 0x19, 0x4f, 0xe3, 0x93, 0x05, 0xb6, 0xda, 0x11, 0x14, 0xc1, 0xd6, 0xc1, 0xe9, + 0xbd, 0x7f, 0xe9, 0xdf, 0xbd, 0xbf, 0xa2, 0xda, 0xf0, 0xbe, 0x00, 0x5b, 0x37, 0xb2, 0xf4, 0x9a, + 0x73, 0xc5, 0xb9, 0x9b, 0xbf, 0x3d, 0x82, 0x83, 0xf9, 0xcb, 0xa5, 0x87, 0x70, 0x65, 0xde, 0x07, + 0xbd, 0xb3, 0x2c, 0xf7, 0x59, 0xbb, 0xee, 0xdd, 0x95, 0xb4, 0x1a, 0xb8, 0xf9, 0x66, 0x7a, 0xea, + 0x15, 0xbe, 0x9e, 0x7a, 0x85, 0x0f, 0x33, 0x8f, 0x4c, 0x67, 0x1e, 0xf9, 0x32, 0xf3, 0xc8, 0xf7, + 0x99, 0x47, 0xde, 0xee, 0xfd, 0xe7, 0x6f, 0xec, 0xb1, 0x9e, 0x3a, 0xb6, 0x8a, 0xf2, 0xf0, 0x67, + 0x00, 0x00, 0x00, 0xff, 0xff, 0xaf, 0xd6, 0x7f, 0x24, 0x0f, 0x05, 0x00, 0x00, +} diff --git a/api/services/leases/v1/leases.proto b/api/services/leases/v1/leases.proto new file mode 100644 index 000000000..ab9e7a6dd --- /dev/null +++ b/api/services/leases/v1/leases.proto @@ -0,0 +1,64 @@ +syntax = "proto3"; + +package containerd.services.leases.v1; + +import "gogoproto/gogo.proto"; +import "google/protobuf/empty.proto"; +import "google/protobuf/timestamp.proto"; + +option go_package = "github.com/containerd/containerd/api/services/leases/v1;leases"; + +// Leases service manages resources leases within the metadata store. +service Leases { + // Create creates a new lease for managing changes to metadata. A lease + // can be used to protect objects from being removed. + rpc Create(CreateRequest) returns (CreateResponse); + + // Delete deletes the lease and makes any unreferenced objects created + // during the lease eligible for garbage collection if not referenced + // or retained by other resources during the lease. + rpc Delete(DeleteRequest) returns (google.protobuf.Empty); + + // ListTransactions lists all active leases, returning the full list of + // leases and optionally including the referenced resources. + rpc List(ListRequest) returns (ListResponse); +} + +// Snapshot is a snapshot resource reference. +message Snapshot { + string snapshotter = 1; + string key = 2; +} + +// Lease is an object which retains resources while it exists. +message Lease { + string id = 1; + + google.protobuf.Timestamp created_at = 2 [(gogoproto.stdtime) = true, (gogoproto.nullable) = false]; + + map labels = 3; +} + +message CreateRequest { + // ID is used to identity the lease, when the id is not set the service + // generates a random identifier for the lease. + string id = 1; + + map labels = 3; +} + +message CreateResponse { + Lease lease = 1; +} + +message DeleteRequest { + string id = 1; +} + +message ListRequest { + repeated string filters = 1; +} + +message ListResponse { + repeated Lease leases = 1; +} diff --git a/client.go b/client.go index ad41ee57e..14216db0c 100644 --- a/client.go +++ b/client.go @@ -25,6 +25,7 @@ import ( "github.com/containerd/containerd/diff" "github.com/containerd/containerd/errdefs" "github.com/containerd/containerd/images" + "github.com/containerd/containerd/leases" "github.com/containerd/containerd/namespaces" "github.com/containerd/containerd/platforms" "github.com/containerd/containerd/plugin" @@ -137,6 +138,14 @@ func (c *Client) Containers(ctx context.Context, filters ...string) ([]Container // NewContainer will create a new container in container with the provided id // the id must be unique within the namespace func (c *Client) NewContainer(ctx context.Context, id string, opts ...NewContainerOpts) (Container, error) { + l, err := c.CreateLease(ctx) + if err != nil { + return nil, err + } + defer l.Delete(ctx) + + ctx = leases.WithLease(ctx, l.ID()) + container := containers.Container{ ID: id, Runtime: containers.RuntimeInfo{ @@ -212,6 +221,14 @@ func (c *Client) Pull(ctx context.Context, ref string, opts ...RemoteOpt) (Image } store := c.ContentStore() + l, err := c.CreateLease(ctx) + if err != nil { + return nil, err + } + defer l.Delete(ctx) + + ctx = leases.WithLease(ctx, l.ID()) + name, desc, err := pullCtx.Resolver.Resolve(ctx, ref) if err != nil { return nil, err @@ -583,6 +600,15 @@ func (c *Client) Import(ctx context.Context, ref string, reader io.Reader, opts if err != nil { return nil, err } + + l, err := c.CreateLease(ctx) + if err != nil { + return nil, err + } + defer l.Delete(ctx) + + ctx = leases.WithLease(ctx, l.ID()) + switch iopts.format { case ociImageFormat: return c.importFromOCITar(ctx, ref, reader, iopts) diff --git a/cmd/containerd/builtins.go b/cmd/containerd/builtins.go index 6ee3f8de7..2112548a6 100644 --- a/cmd/containerd/builtins.go +++ b/cmd/containerd/builtins.go @@ -10,6 +10,7 @@ import ( _ "github.com/containerd/containerd/services/healthcheck" _ "github.com/containerd/containerd/services/images" _ "github.com/containerd/containerd/services/introspection" + _ "github.com/containerd/containerd/services/leases" _ "github.com/containerd/containerd/services/namespaces" _ "github.com/containerd/containerd/services/snapshot" _ "github.com/containerd/containerd/services/tasks" diff --git a/lease.go b/lease.go new file mode 100644 index 000000000..7348d59fa --- /dev/null +++ b/lease.go @@ -0,0 +1,71 @@ +package containerd + +import ( + "context" + "time" + + leasesapi "github.com/containerd/containerd/api/services/leases/v1" +) + +// 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 +func (c *Client) CreateLease(ctx context.Context) (Lease, error) { + lapi := leasesapi.NewLeasesClient(c.conn) + resp, err := lapi.Create(ctx, &leasesapi.CreateRequest{}) + if err != nil { + return Lease{}, err + } + + return Lease{ + id: resp.Lease.ID, + client: c, + }, nil +} + +// ListLeases lists active leases +func (c *Client) ListLeases(ctx context.Context) ([]Lease, error) { + lapi := leasesapi.NewLeasesClient(c.conn) + 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, + client: c, + } + } + + return leases, 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 := leasesapi.NewLeasesClient(l.client.conn) + _, err := lapi.Delete(ctx, &leasesapi.DeleteRequest{ + ID: l.id, + }) + return err +} diff --git a/leases/context.go b/leases/context.go new file mode 100644 index 000000000..cfd7e4a46 --- /dev/null +++ b/leases/context.go @@ -0,0 +1,24 @@ +package leases + +import "context" + +type leaseKey struct{} + +// WithLease sets a given lease on the context +func WithLease(ctx context.Context, lid string) context.Context { + ctx = context.WithValue(ctx, leaseKey{}, lid) + + // also store on the grpc headers so it gets picked up by any clients that + // are using this. + return withGRPCLeaseHeader(ctx, lid) +} + +// Lease returns the lease from the context. +func Lease(ctx context.Context) (string, bool) { + lid, ok := ctx.Value(leaseKey{}).(string) + if !ok { + return fromGRPCHeader(ctx) + } + + return lid, ok +} diff --git a/leases/grpc.go b/leases/grpc.go new file mode 100644 index 000000000..cea5b25fe --- /dev/null +++ b/leases/grpc.go @@ -0,0 +1,41 @@ +package leases + +import ( + "golang.org/x/net/context" + "google.golang.org/grpc/metadata" +) + +const ( + // GRPCHeader defines the header name for specifying a containerd lease. + GRPCHeader = "containerd-lease" +) + +func withGRPCLeaseHeader(ctx context.Context, lid string) context.Context { + // also store on the grpc headers so it gets picked up by any clients + // that are using this. + txheader := metadata.Pairs(GRPCHeader, lid) + md, ok := metadata.FromOutgoingContext(ctx) // merge with outgoing context. + if !ok { + md = txheader + } else { + // order ensures the latest is first in this list. + md = metadata.Join(txheader, md) + } + + return metadata.NewOutgoingContext(ctx, md) +} + +func fromGRPCHeader(ctx context.Context) (string, bool) { + // try to extract for use in grpc servers. + md, ok := metadata.FromIncomingContext(ctx) + if !ok { + return "", false + } + + values := md[GRPCHeader] + if len(values) == 0 { + return "", false + } + + return values[0], true +} diff --git a/metadata/buckets.go b/metadata/buckets.go index 43849e080..b6a66ba48 100644 --- a/metadata/buckets.go +++ b/metadata/buckets.go @@ -38,6 +38,7 @@ var ( bucketKeyObjectContent = []byte("content") // stores content references bucketKeyObjectBlob = []byte("blob") // stores content links bucketKeyObjectIngest = []byte("ingest") // stores ingest links + bucketKeyObjectLeases = []byte("leases") // stores leases bucketKeyDigest = []byte("digest") bucketKeyMediaType = []byte("mediatype") @@ -53,6 +54,7 @@ var ( bucketKeySnapshotter = []byte("snapshotter") bucketKeyTarget = []byte("target") bucketKeyExtensions = []byte("extensions") + bucketKeyCreatedAt = []byte("createdat") ) func getBucket(tx *bolt.Tx, keys ...[]byte) *bolt.Bucket { diff --git a/metadata/content.go b/metadata/content.go index 293539aa1..0797345e2 100644 --- a/metadata/content.go +++ b/metadata/content.go @@ -391,27 +391,31 @@ func (nw *namespacedWriter) Commit(ctx context.Context, size int64, expected dig return err } } - return nw.commit(ctx, tx, size, expected, opts...) + dgst, err := nw.commit(ctx, tx, size, expected, opts...) + if err != nil { + return err + } + return addContentLease(ctx, tx, dgst) }) } -func (nw *namespacedWriter) commit(ctx context.Context, tx *bolt.Tx, size int64, expected digest.Digest, opts ...content.Opt) error { +func (nw *namespacedWriter) commit(ctx context.Context, tx *bolt.Tx, size int64, expected digest.Digest, opts ...content.Opt) (digest.Digest, error) { var base content.Info for _, opt := range opts { if err := opt(&base); err != nil { - return err + return "", err } } if err := validateInfo(&base); err != nil { - return err + return "", err } status, err := nw.Writer.Status() if err != nil { - return err + return "", err } if size != 0 && size != status.Offset { - return errors.Errorf("%q failed size validation: %v != %v", nw.ref, status.Offset, size) + return "", errors.Errorf("%q failed size validation: %v != %v", nw.ref, status.Offset, size) } size = status.Offset @@ -419,32 +423,32 @@ func (nw *namespacedWriter) commit(ctx context.Context, tx *bolt.Tx, size int64, if err := nw.Writer.Commit(ctx, size, expected); err != nil { if !errdefs.IsAlreadyExists(err) { - return err + return "", err } if getBlobBucket(tx, nw.namespace, actual) != nil { - return errors.Wrapf(errdefs.ErrAlreadyExists, "content %v", actual) + return "", errors.Wrapf(errdefs.ErrAlreadyExists, "content %v", actual) } } bkt, err := createBlobBucket(tx, nw.namespace, actual) if err != nil { - return err + return "", err } commitTime := time.Now().UTC() sizeEncoded, err := encodeInt(size) if err != nil { - return err + return "", err } if err := boltutil.WriteTimestamps(bkt, commitTime, commitTime); err != nil { - return err + return "", err } if err := boltutil.WriteLabels(bkt, base.Labels); err != nil { - return err + return "", err } - return bkt.Put(bucketKeySize, sizeEncoded) + return actual, bkt.Put(bucketKeySize, sizeEncoded) } func (nw *namespacedWriter) Status() (content.Status, error) { diff --git a/metadata/gc.go b/metadata/gc.go index 63d1852e0..7fe6f7da2 100644 --- a/metadata/gc.go +++ b/metadata/gc.go @@ -46,6 +46,55 @@ func scanRoots(ctx context.Context, tx *bolt.Tx, nc chan<- gc.Node) error { nbkt := v1bkt.Bucket(k) ns := string(k) + lbkt := nbkt.Bucket(bucketKeyObjectLeases) + if lbkt != nil { + if err := lbkt.ForEach(func(k, v []byte) error { + if v != nil { + return nil + } + libkt := lbkt.Bucket(k) + + cbkt := libkt.Bucket(bucketKeyObjectContent) + if cbkt != nil { + if err := cbkt.ForEach(func(k, v []byte) error { + select { + case nc <- gcnode(ResourceContent, ns, string(k)): + case <-ctx.Done(): + return ctx.Err() + } + return nil + }); err != nil { + return err + } + } + + sbkt := libkt.Bucket(bucketKeyObjectSnapshots) + if sbkt != nil { + if err := sbkt.ForEach(func(sk, sv []byte) error { + if sv != nil { + return nil + } + snbkt := sbkt.Bucket(sk) + + return snbkt.ForEach(func(k, v []byte) error { + select { + case nc <- gcnode(ResourceSnapshot, ns, fmt.Sprintf("%s/%s", sk, k)): + case <-ctx.Done(): + return ctx.Err() + } + return nil + }) + }); err != nil { + return err + } + } + + return nil + }); err != nil { + return err + } + } + ibkt := nbkt.Bucket(bucketKeyObjectImages) if ibkt != nil { if err := ibkt.ForEach(func(k, v []byte) error { diff --git a/metadata/gc_test.go b/metadata/gc_test.go index 0d6199071..9cef83269 100644 --- a/metadata/gc_test.go +++ b/metadata/gc_test.go @@ -34,14 +34,22 @@ func TestGCRoots(t *testing.T) { addSnapshot("ns1", "overlay", "sn1", "", nil), addSnapshot("ns1", "overlay", "sn2", "", nil), addSnapshot("ns1", "overlay", "sn3", "", labelmap(string(labelGCRoot), "always")), + addLeaseSnapshot("ns2", "l1", "overlay", "sn5"), + addLeaseSnapshot("ns2", "l2", "overlay", "sn6"), + addLeaseContent("ns2", "l1", dgst(4)), + addLeaseContent("ns2", "l2", dgst(5)), } expected := []gc.Node{ gcnode(ResourceContent, "ns1", dgst(1).String()), gcnode(ResourceContent, "ns1", dgst(2).String()), gcnode(ResourceContent, "ns2", dgst(2).String()), + gcnode(ResourceContent, "ns2", dgst(4).String()), + gcnode(ResourceContent, "ns2", dgst(5).String()), gcnode(ResourceSnapshot, "ns1", "overlay/sn2"), gcnode(ResourceSnapshot, "ns1", "overlay/sn3"), + gcnode(ResourceSnapshot, "ns2", "overlay/sn5"), + gcnode(ResourceSnapshot, "ns2", "overlay/sn6"), } if err := db.Update(func(tx *bolt.Tx) error { @@ -374,6 +382,26 @@ func addContent(ns string, dgst digest.Digest, labels map[string]string) alterFu } } +func addLeaseSnapshot(ns, lid, snapshotter, name string) alterFunc { + return func(bkt *bolt.Bucket) error { + sbkt, err := createBuckets(bkt, ns, string(bucketKeyObjectLeases), lid, string(bucketKeyObjectSnapshots), snapshotter) + if err != nil { + return err + } + return sbkt.Put([]byte(name), nil) + } +} + +func addLeaseContent(ns, lid string, dgst digest.Digest) alterFunc { + return func(bkt *bolt.Bucket) error { + cbkt, err := createBuckets(bkt, ns, string(bucketKeyObjectLeases), lid, string(bucketKeyObjectContent)) + if err != nil { + return err + } + return cbkt.Put([]byte(dgst.String()), nil) + } +} + func addContainer(ns, name, snapshotter, snapshot string, labels map[string]string) alterFunc { return func(bkt *bolt.Bucket) error { cbkt, err := createBuckets(bkt, ns, string(bucketKeyObjectContainers), name) diff --git a/metadata/leases.go b/metadata/leases.go new file mode 100644 index 000000000..006123d45 --- /dev/null +++ b/metadata/leases.go @@ -0,0 +1,201 @@ +package metadata + +import ( + "context" + "time" + + "github.com/boltdb/bolt" + "github.com/containerd/containerd/errdefs" + "github.com/containerd/containerd/leases" + "github.com/containerd/containerd/metadata/boltutil" + "github.com/containerd/containerd/namespaces" + digest "github.com/opencontainers/go-digest" + "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 +// and also returns existing leases +type LeaseManager struct { + tx *bolt.Tx +} + +// NewLeaseManager creates a new lease manager for managing leases using +// the provided database transaction. +func NewLeaseManager(tx *bolt.Tx) *LeaseManager { + return &LeaseManager{ + tx: tx, + } +} + +// Create creates a new lease using the provided lease +func (lm *LeaseManager) Create(ctx context.Context, lid string, labels map[string]string) (Lease, error) { + namespace, err := namespaces.NamespaceRequired(ctx) + if err != nil { + return Lease{}, err + } + + topbkt, err := createBucketIfNotExists(lm.tx, bucketKeyVersion, []byte(namespace), bucketKeyObjectLeases) + if err != nil { + return Lease{}, err + } + + txbkt, err := topbkt.CreateBucket([]byte(lid)) + if err != nil { + if err == bolt.ErrBucketExists { + err = errdefs.ErrAlreadyExists + } + return Lease{}, err + } + + t := time.Now().UTC() + createdAt, err := t.MarshalBinary() + if err != nil { + return Lease{}, err + } + if err := txbkt.Put(bucketKeyCreatedAt, createdAt); err != nil { + return Lease{}, err + } + + if labels != nil { + if err := boltutil.WriteLabels(txbkt, labels); err != nil { + return Lease{}, err + } + } + + return Lease{ + ID: lid, + CreatedAt: t, + Labels: labels, + }, nil +} + +// Delete delets the lease with the provided lease ID +func (lm *LeaseManager) Delete(ctx context.Context, lid string) error { + namespace, err := namespaces.NamespaceRequired(ctx) + if err != nil { + return err + } + + topbkt := getBucket(lm.tx, bucketKeyVersion, []byte(namespace), bucketKeyObjectLeases) + if topbkt == nil { + return nil + } + if err := topbkt.DeleteBucket([]byte(lid)); err != nil && err != bolt.ErrBucketNotFound { + return err + } + return nil +} + +// List lists all active leases +func (lm *LeaseManager) List(ctx context.Context, includeResources bool, filter ...string) ([]Lease, error) { + namespace, err := namespaces.NamespaceRequired(ctx) + if err != nil { + return nil, err + } + + var leases []Lease + + topbkt := getBucket(lm.tx, bucketKeyVersion, []byte(namespace), bucketKeyObjectLeases) + if topbkt == nil { + return leases, nil + } + + if err := topbkt.ForEach(func(k, v []byte) error { + if v != nil { + return nil + } + txbkt := topbkt.Bucket(k) + + l := Lease{ + ID: string(k), + } + + if v := txbkt.Get(bucketKeyCreatedAt); v != nil { + t := &l.CreatedAt + if err := t.UnmarshalBinary(v); err != nil { + return err + } + } + + labels, err := boltutil.ReadLabels(txbkt) + if err != nil { + return err + } + l.Labels = labels + + // TODO: Read Snapshots + // TODO: Read Content + + leases = append(leases, l) + + return nil + }); err != nil { + return nil, err + } + + return leases, nil +} + +func addSnapshotLease(ctx context.Context, tx *bolt.Tx, snapshotter, key string) error { + lid, ok := leases.Lease(ctx) + if !ok { + return nil + } + + namespace, ok := namespaces.Namespace(ctx) + if !ok { + panic("namespace must already be required") + } + + bkt := getBucket(tx, bucketKeyVersion, []byte(namespace), bucketKeyObjectLeases, []byte(lid)) + if bkt == nil { + return errors.Wrap(errdefs.ErrNotFound, "lease does not exist") + } + + bkt, err := bkt.CreateBucketIfNotExists(bucketKeyObjectSnapshots) + if err != nil { + return err + } + + bkt, err = bkt.CreateBucketIfNotExists([]byte(snapshotter)) + if err != nil { + return err + } + + return bkt.Put([]byte(key), nil) +} + +func addContentLease(ctx context.Context, tx *bolt.Tx, dgst digest.Digest) error { + lid, ok := leases.Lease(ctx) + if !ok { + return nil + } + + namespace, ok := namespaces.Namespace(ctx) + if !ok { + panic("namespace must already be required") + } + + bkt := getBucket(tx, bucketKeyVersion, []byte(namespace), bucketKeyObjectLeases, []byte(lid)) + if bkt == nil { + return errors.Wrap(errdefs.ErrNotFound, "lease does not exist") + } + + bkt, err := bkt.CreateBucketIfNotExists(bucketKeyObjectContent) + if err != nil { + return err + } + + return bkt.Put([]byte(dgst.String()), nil) +} diff --git a/metadata/leases_test.go b/metadata/leases_test.go new file mode 100644 index 000000000..f83da5bbb --- /dev/null +++ b/metadata/leases_test.go @@ -0,0 +1,90 @@ +package metadata + +import ( + "testing" + + "github.com/boltdb/bolt" + "github.com/containerd/containerd/errdefs" + "github.com/pkg/errors" +) + +func TestLeases(t *testing.T) { + ctx, db, cancel := testEnv(t) + defer cancel() + + testCases := []struct { + ID string + Cause error + }{ + { + ID: "tx1", + }, + { + ID: "tx1", + Cause: errdefs.ErrAlreadyExists, + }, + { + ID: "tx2", + }, + } + + var leases []Lease + + for _, tc := range testCases { + if err := db.Update(func(tx *bolt.Tx) error { + lease, err := NewLeaseManager(tx).Create(ctx, tc.ID, nil) + if err != nil { + if tc.Cause != nil && errors.Cause(err) == tc.Cause { + return nil + } + return err + } + leases = append(leases, lease) + return nil + }); err != nil { + t.Fatal(err) + } + } + + var listed []Lease + // List leases, check same + if err := db.View(func(tx *bolt.Tx) error { + var err error + listed, err = NewLeaseManager(tx).List(ctx, false) + return err + }); err != nil { + t.Fatal(err) + } + + if len(listed) != len(leases) { + t.Fatalf("Expected %d lease, got %d", len(leases), len(listed)) + } + for i := range listed { + if listed[i].ID != leases[i].ID { + t.Fatalf("Expected lease ID %s, got %s", leases[i].ID, listed[i].ID) + } + if listed[i].CreatedAt != leases[i].CreatedAt { + t.Fatalf("Expected lease created at time %s, got %s", leases[i].CreatedAt, listed[i].CreatedAt) + } + } + + for _, tc := range testCases { + if err := db.Update(func(tx *bolt.Tx) error { + return NewLeaseManager(tx).Delete(ctx, tc.ID) + }); err != nil { + t.Fatal(err) + } + } + + if err := db.View(func(tx *bolt.Tx) error { + var err error + listed, err = NewLeaseManager(tx).List(ctx, false) + return err + }); err != nil { + t.Fatal(err) + } + + if len(listed) > 0 { + t.Fatalf("Expected no leases, found %d: %v", len(listed), listed) + } +} diff --git a/metadata/snapshot.go b/metadata/snapshot.go index ad38e5915..22ce3c8c0 100644 --- a/metadata/snapshot.go +++ b/metadata/snapshot.go @@ -326,6 +326,10 @@ func (s *snapshotter) createSnapshot(ctx context.Context, key, parent string, re return err } + if err := addSnapshotLease(ctx, tx, s.name, key); err != nil { + return err + } + // TODO: Consider doing this outside of transaction to lessen // metadata lock time if readonly { diff --git a/server/server.go b/server/server.go index d1a58e915..e8a5f877e 100644 --- a/server/server.go +++ b/server/server.go @@ -16,6 +16,7 @@ import ( eventsapi "github.com/containerd/containerd/api/services/events/v1" images "github.com/containerd/containerd/api/services/images/v1" introspection "github.com/containerd/containerd/api/services/introspection/v1" + leasesapi "github.com/containerd/containerd/api/services/leases/v1" namespaces "github.com/containerd/containerd/api/services/namespaces/v1" snapshotapi "github.com/containerd/containerd/api/services/snapshot/v1" tasks "github.com/containerd/containerd/api/services/tasks/v1" @@ -255,6 +256,8 @@ func interceptor( ctx = log.WithModule(ctx, "events") case introspection.IntrospectionServer: ctx = log.WithModule(ctx, "introspection") + case leasesapi.LeasesServer: + ctx = log.WithModule(ctx, "leases") default: log.G(ctx).Warnf("unknown GRPC server type: %#v\n", info.Server) } diff --git a/services/leases/service.go b/services/leases/service.go new file mode 100644 index 000000000..01176d74a --- /dev/null +++ b/services/leases/service.go @@ -0,0 +1,115 @@ +package leases + +import ( + "crypto/rand" + "encoding/base64" + "fmt" + "time" + + "google.golang.org/grpc" + + "github.com/boltdb/bolt" + api "github.com/containerd/containerd/api/services/leases/v1" + "github.com/containerd/containerd/metadata" + "github.com/containerd/containerd/plugin" + "github.com/golang/protobuf/ptypes/empty" + "golang.org/x/net/context" +) + +func init() { + plugin.Register(&plugin.Registration{ + Type: plugin.GRPCPlugin, + ID: "leases", + Requires: []plugin.Type{ + plugin.MetadataPlugin, + }, + InitFn: func(ic *plugin.InitContext) (interface{}, error) { + m, err := ic.Get(plugin.MetadataPlugin) + if err != nil { + return nil, err + } + return NewService(m.(*metadata.DB)), nil + }, + }) +} + +type service struct { + db *metadata.DB +} + +// NewService returns the GRPC metadata server +func NewService(db *metadata.DB) api.LeasesServer { + return &service{ + db: db, + } +} + +func (s *service) Register(server *grpc.Server) error { + api.RegisterLeasesServer(server, s) + return nil +} + +func (s *service) Create(ctx context.Context, r *api.CreateRequest) (*api.CreateResponse, error) { + lid := r.ID + if lid == "" { + lid = generateLeaseID() + } + var trans metadata.Lease + if err := s.db.Update(func(tx *bolt.Tx) error { + var err error + trans, err = metadata.NewLeaseManager(tx).Create(ctx, lid, r.Labels) + return err + }); err != nil { + return nil, err + } + return &api.CreateResponse{ + Lease: txToGRPC(trans), + }, nil +} + +func (s *service) Delete(ctx context.Context, r *api.DeleteRequest) (*empty.Empty, error) { + if err := s.db.Update(func(tx *bolt.Tx) error { + return metadata.NewLeaseManager(tx).Delete(ctx, r.ID) + }); err != nil { + return nil, err + } + return &empty.Empty{}, nil +} + +func (s *service) List(ctx context.Context, r *api.ListRequest) (*api.ListResponse, error) { + var leases []metadata.Lease + if err := s.db.View(func(tx *bolt.Tx) error { + var err error + leases, err = metadata.NewLeaseManager(tx).List(ctx, false, r.Filters...) + return err + }); err != nil { + return nil, err + } + + 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[:])) +} diff --git a/task.go b/task.go index b5f8450b3..6eda83d15 100644 --- a/task.go +++ b/task.go @@ -18,6 +18,7 @@ import ( "github.com/containerd/containerd/diff" "github.com/containerd/containerd/errdefs" "github.com/containerd/containerd/images" + "github.com/containerd/containerd/leases" "github.com/containerd/containerd/log" "github.com/containerd/containerd/mount" "github.com/containerd/containerd/plugin" @@ -359,6 +360,14 @@ func (t *task) Resize(ctx context.Context, w, h uint32) error { } func (t *task) Checkpoint(ctx context.Context, opts ...CheckpointTaskOpts) (Image, error) { + l, err := t.client.CreateLease(ctx) + if err != nil { + return nil, err + } + defer l.Delete(ctx) + + ctx = leases.WithLease(ctx, l.ID()) + request := &tasks.CheckpointTaskRequest{ ContainerID: t.id, }