Explorar el Código

*: add support for lease create and revoke

Basic support for lease operations like create and revoke.
We still need to:
1. attach keys to leases in KV implmentation if lease field is set
2. leader periodically removes expired leases
3. leader serves keepAlive requests and follower forwards keepAlive
requests to leader.
Xiang Li hace 10 años
padre
commit
d9ca929a33

+ 123 - 0
etcdctlv3/command/lease_command.go

@@ -0,0 +1,123 @@
+// Copyright 2016 CoreOS, Inc.
+//
+// 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 command
+
+import (
+	"fmt"
+	"os"
+	"strconv"
+
+	"github.com/coreos/etcd/Godeps/_workspace/src/github.com/spf13/cobra"
+	"github.com/coreos/etcd/Godeps/_workspace/src/golang.org/x/net/context"
+	"github.com/coreos/etcd/Godeps/_workspace/src/google.golang.org/grpc"
+	pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
+)
+
+// NewLeaseCommand returns the cobra command for "lease".
+func NewLeaseCommand() *cobra.Command {
+	lc := &cobra.Command{
+		Use:   "lease",
+		Short: "lease is used to manage leases.",
+	}
+
+	lc.AddCommand(NewLeaseCreateCommand())
+	lc.AddCommand(NewLeaseRevokeCommand())
+
+	return lc
+}
+
+// NewLeaseCreateCommand returns the cobra command for "lease create".
+func NewLeaseCreateCommand() *cobra.Command {
+	lc := &cobra.Command{
+		Use:   "create",
+		Short: "create is used to create leases.",
+
+		Run: leaseCreateCommandFunc,
+	}
+
+	return lc
+}
+
+// leaseCreateCommandFunc executes the "lease create" command.
+func leaseCreateCommandFunc(cmd *cobra.Command, args []string) {
+	if len(args) != 1 {
+		ExitWithError(ExitBadArgs, fmt.Errorf("lease create command needs TTL arguement."))
+	}
+
+	ttl, err := strconv.ParseInt(args[0], 10, 64)
+	if err != nil {
+		ExitWithError(ExitBadArgs, fmt.Errorf("bad TTL (%v)", err))
+	}
+
+	endpoint, err := cmd.Flags().GetString("endpoint")
+	if err != nil {
+		ExitWithError(ExitError, err)
+	}
+	conn, err := grpc.Dial(endpoint)
+	if err != nil {
+		ExitWithError(ExitBadConnection, err)
+	}
+	lease := pb.NewLeaseClient(conn)
+
+	req := &pb.LeaseCreateRequest{TTL: ttl}
+	resp, err := lease.LeaseCreate(context.Background(), req)
+	if err != nil {
+		fmt.Fprintf(os.Stderr, "failed to create lease (%v)\n", err)
+		return
+	}
+	fmt.Printf("lease %016x created with TTL(%ds)\n", resp.ID, resp.TTL)
+}
+
+// NewLeaseRevokeCommand returns the cobra command for "lease revoke".
+func NewLeaseRevokeCommand() *cobra.Command {
+	lc := &cobra.Command{
+		Use:   "revoke",
+		Short: "revoke is used to revoke leases.",
+
+		Run: leaseRevokeCommandFunc,
+	}
+
+	return lc
+}
+
+// leaseRevokeCommandFunc executes the "lease create" command.
+func leaseRevokeCommandFunc(cmd *cobra.Command, args []string) {
+	if len(args) != 1 {
+		ExitWithError(ExitBadArgs, fmt.Errorf("lease revoke command needs 1 argument"))
+	}
+
+	id, err := strconv.ParseInt(args[0], 16, 64)
+	if err != nil {
+		ExitWithError(ExitBadArgs, fmt.Errorf("bad lease ID arg (%v), expecting ID in Hex", err))
+	}
+
+	endpoint, err := cmd.Flags().GetString("endpoint")
+	if err != nil {
+		ExitWithError(ExitError, err)
+	}
+	conn, err := grpc.Dial(endpoint)
+	if err != nil {
+		ExitWithError(ExitBadConnection, err)
+	}
+	lease := pb.NewLeaseClient(conn)
+
+	req := &pb.LeaseRevokeRequest{ID: id}
+	_, err = lease.LeaseRevoke(context.Background(), req)
+	if err != nil {
+		fmt.Fprintf(os.Stderr, "failed to revoke lease (%v)\n", err)
+		return
+	}
+	fmt.Printf("lease %016x revoked\n", id)
+}

+ 1 - 0
etcdctlv3/main.go

@@ -51,6 +51,7 @@ func init() {
 		command.NewCompactionCommand(),
 		command.NewCompactionCommand(),
 		command.NewWatchCommand(),
 		command.NewWatchCommand(),
 		command.NewVersionCommand(),
 		command.NewVersionCommand(),
+		command.NewLeaseCommand(),
 	)
 	)
 }
 }
 
 

+ 1 - 0
etcdmain/etcd.go

@@ -324,6 +324,7 @@ func startEtcd(cfg *config) (<-chan struct{}, error) {
 		grpcServer := grpc.NewServer()
 		grpcServer := grpc.NewServer()
 		etcdserverpb.RegisterKVServer(grpcServer, v3rpc.NewKVServer(s))
 		etcdserverpb.RegisterKVServer(grpcServer, v3rpc.NewKVServer(s))
 		etcdserverpb.RegisterWatchServer(grpcServer, v3rpc.NewWatchServer(s))
 		etcdserverpb.RegisterWatchServer(grpcServer, v3rpc.NewWatchServer(s))
+		etcdserverpb.RegisterLeaseServer(grpcServer, v3rpc.NewLeaseServer(s))
 		go func() { plog.Fatal(grpcServer.Serve(v3l)) }()
 		go func() { plog.Fatal(grpcServer.Serve(v3l)) }()
 	}
 	}
 
 

+ 4 - 3
etcdserver/api/v3rpc/error.go

@@ -21,7 +21,8 @@ import (
 )
 )
 
 
 var (
 var (
-	ErrEmptyKey  = grpc.Errorf(codes.InvalidArgument, "key is not provided")
-	ErrCompacted = grpc.Errorf(codes.OutOfRange, storage.ErrCompacted.Error())
-	ErrFutureRev = grpc.Errorf(codes.OutOfRange, storage.ErrFutureRev.Error())
+	ErrEmptyKey      = grpc.Errorf(codes.InvalidArgument, "key is not provided")
+	ErrCompacted     = grpc.Errorf(codes.OutOfRange, storage.ErrCompacted.Error())
+	ErrFutureRev     = grpc.Errorf(codes.OutOfRange, storage.ErrFutureRev.Error())
+	ErrLeaseNotFound = grpc.Errorf(codes.NotFound, "requested lease not found")
 )
 )

+ 45 - 0
etcdserver/api/v3rpc/lease.go

@@ -0,0 +1,45 @@
+// Copyright 2016 CoreOS, Inc.
+//
+// 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 v3rpc
+
+import (
+	"github.com/coreos/etcd/Godeps/_workspace/src/golang.org/x/net/context"
+	"github.com/coreos/etcd/etcdserver"
+	pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
+)
+
+type LeaseServer struct {
+	le etcdserver.Lessor
+}
+
+func NewLeaseServer(le etcdserver.Lessor) pb.LeaseServer {
+	return &LeaseServer{le: le}
+}
+
+func (ls *LeaseServer) LeaseCreate(ctx context.Context, cr *pb.LeaseCreateRequest) (*pb.LeaseCreateResponse, error) {
+	return ls.le.LeaseCreate(ctx, cr)
+}
+
+func (ls *LeaseServer) LeaseRevoke(ctx context.Context, rr *pb.LeaseRevokeRequest) (*pb.LeaseRevokeResponse, error) {
+	r, err := ls.le.LeaseRevoke(ctx, rr)
+	if err != nil {
+		return nil, ErrLeaseNotFound
+	}
+	return r, nil
+}
+
+func (ls *LeaseServer) LeaseKeepAlive(stream pb.Lease_LeaseKeepAliveServer) error {
+	panic("not implemented")
+}

+ 90 - 0
etcdserver/etcdserverpb/raft_internal.pb.go

@@ -24,6 +24,8 @@ type InternalRaftRequest struct {
 	DeleteRange *DeleteRangeRequest `protobuf:"bytes,5,opt,name=delete_range" json:"delete_range,omitempty"`
 	DeleteRange *DeleteRangeRequest `protobuf:"bytes,5,opt,name=delete_range" json:"delete_range,omitempty"`
 	Txn         *TxnRequest         `protobuf:"bytes,6,opt,name=txn" json:"txn,omitempty"`
 	Txn         *TxnRequest         `protobuf:"bytes,6,opt,name=txn" json:"txn,omitempty"`
 	Compaction  *CompactionRequest  `protobuf:"bytes,7,opt,name=compaction" json:"compaction,omitempty"`
 	Compaction  *CompactionRequest  `protobuf:"bytes,7,opt,name=compaction" json:"compaction,omitempty"`
+	LeaseCreate *LeaseCreateRequest `protobuf:"bytes,8,opt,name=lease_create" json:"lease_create,omitempty"`
+	LeaseRevoke *LeaseRevokeRequest `protobuf:"bytes,9,opt,name=lease_revoke" json:"lease_revoke,omitempty"`
 }
 }
 
 
 func (m *InternalRaftRequest) Reset()         { *m = InternalRaftRequest{} }
 func (m *InternalRaftRequest) Reset()         { *m = InternalRaftRequest{} }
@@ -117,6 +119,26 @@ func (m *InternalRaftRequest) MarshalTo(data []byte) (int, error) {
 		}
 		}
 		i += n6
 		i += n6
 	}
 	}
+	if m.LeaseCreate != nil {
+		data[i] = 0x42
+		i++
+		i = encodeVarintRaftInternal(data, i, uint64(m.LeaseCreate.Size()))
+		n7, err := m.LeaseCreate.MarshalTo(data[i:])
+		if err != nil {
+			return 0, err
+		}
+		i += n7
+	}
+	if m.LeaseRevoke != nil {
+		data[i] = 0x4a
+		i++
+		i = encodeVarintRaftInternal(data, i, uint64(m.LeaseRevoke.Size()))
+		n8, err := m.LeaseRevoke.MarshalTo(data[i:])
+		if err != nil {
+			return 0, err
+		}
+		i += n8
+	}
 	return i, nil
 	return i, nil
 }
 }
 
 
@@ -195,6 +217,14 @@ func (m *InternalRaftRequest) Size() (n int) {
 		l = m.Compaction.Size()
 		l = m.Compaction.Size()
 		n += 1 + l + sovRaftInternal(uint64(l))
 		n += 1 + l + sovRaftInternal(uint64(l))
 	}
 	}
+	if m.LeaseCreate != nil {
+		l = m.LeaseCreate.Size()
+		n += 1 + l + sovRaftInternal(uint64(l))
+	}
+	if m.LeaseRevoke != nil {
+		l = m.LeaseRevoke.Size()
+		n += 1 + l + sovRaftInternal(uint64(l))
+	}
 	return n
 	return n
 }
 }
 
 
@@ -432,6 +462,66 @@ func (m *InternalRaftRequest) Unmarshal(data []byte) error {
 				return err
 				return err
 			}
 			}
 			iNdEx = postIndex
 			iNdEx = postIndex
+		case 8:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field LeaseCreate", wireType)
+			}
+			var msglen int
+			for shift := uint(0); ; shift += 7 {
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := data[iNdEx]
+				iNdEx++
+				msglen |= (int(b) & 0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			if msglen < 0 {
+				return ErrInvalidLengthRaftInternal
+			}
+			postIndex := iNdEx + msglen
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			if m.LeaseCreate == nil {
+				m.LeaseCreate = &LeaseCreateRequest{}
+			}
+			if err := m.LeaseCreate.Unmarshal(data[iNdEx:postIndex]); err != nil {
+				return err
+			}
+			iNdEx = postIndex
+		case 9:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field LeaseRevoke", wireType)
+			}
+			var msglen int
+			for shift := uint(0); ; shift += 7 {
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := data[iNdEx]
+				iNdEx++
+				msglen |= (int(b) & 0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			if msglen < 0 {
+				return ErrInvalidLengthRaftInternal
+			}
+			postIndex := iNdEx + msglen
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			if m.LeaseRevoke == nil {
+				m.LeaseRevoke = &LeaseRevokeRequest{}
+			}
+			if err := m.LeaseRevoke.Unmarshal(data[iNdEx:postIndex]); err != nil {
+				return err
+			}
+			iNdEx = postIndex
 		default:
 		default:
 			var sizeOfWire int
 			var sizeOfWire int
 			for {
 			for {

+ 4 - 0
etcdserver/etcdserverpb/raft_internal.proto

@@ -15,11 +15,15 @@ option (gogoproto.goproto_getters_all) = false;
 message InternalRaftRequest {
 message InternalRaftRequest {
   uint64 ID = 1;
   uint64 ID = 1;
   Request v2 = 2;
   Request v2 = 2;
+
   RangeRequest range = 3;
   RangeRequest range = 3;
   PutRequest put = 4;
   PutRequest put = 4;
   DeleteRangeRequest delete_range = 5;
   DeleteRangeRequest delete_range = 5;
   TxnRequest txn = 6;
   TxnRequest txn = 6;
   CompactionRequest compaction = 7;
   CompactionRequest compaction = 7;
+
+  LeaseCreateRequest lease_create = 8;
+  LeaseRevokeRequest lease_revoke = 9;
 }
 }
 
 
 message EmptyResponse {
 message EmptyResponse {

+ 50 - 75
etcdserver/etcdserverpb/rpc.pb.go

@@ -441,7 +441,7 @@ func (m *WatchResponse) GetEvents() []*storagepb.Event {
 
 
 type LeaseCreateRequest struct {
 type LeaseCreateRequest struct {
 	// advisory ttl in seconds
 	// advisory ttl in seconds
-	Ttl int64 `protobuf:"varint,1,opt,name=ttl,proto3" json:"ttl,omitempty"`
+	TTL int64 `protobuf:"varint,1,opt,proto3" json:"TTL,omitempty"`
 }
 }
 
 
 func (m *LeaseCreateRequest) Reset()         { *m = LeaseCreateRequest{} }
 func (m *LeaseCreateRequest) Reset()         { *m = LeaseCreateRequest{} }
@@ -449,10 +449,10 @@ func (m *LeaseCreateRequest) String() string { return proto.CompactTextString(m)
 func (*LeaseCreateRequest) ProtoMessage()    {}
 func (*LeaseCreateRequest) ProtoMessage()    {}
 
 
 type LeaseCreateResponse struct {
 type LeaseCreateResponse struct {
-	Header  *ResponseHeader `protobuf:"bytes,1,opt,name=header" json:"header,omitempty"`
-	LeaseId int64           `protobuf:"varint,2,opt,name=lease_id,proto3" json:"lease_id,omitempty"`
+	Header *ResponseHeader `protobuf:"bytes,1,opt,name=header" json:"header,omitempty"`
+	ID     int64           `protobuf:"varint,2,opt,proto3" json:"ID,omitempty"`
 	// server decided ttl in second
 	// server decided ttl in second
-	Ttl   int64  `protobuf:"varint,3,opt,name=ttl,proto3" json:"ttl,omitempty"`
+	TTL   int64  `protobuf:"varint,3,opt,proto3" json:"TTL,omitempty"`
 	Error string `protobuf:"bytes,4,opt,name=error,proto3" json:"error,omitempty"`
 	Error string `protobuf:"bytes,4,opt,name=error,proto3" json:"error,omitempty"`
 }
 }
 
 
@@ -468,7 +468,7 @@ func (m *LeaseCreateResponse) GetHeader() *ResponseHeader {
 }
 }
 
 
 type LeaseRevokeRequest struct {
 type LeaseRevokeRequest struct {
-	LeaseId int64 `protobuf:"varint,1,opt,name=lease_id,proto3" json:"lease_id,omitempty"`
+	ID int64 `protobuf:"varint,1,opt,proto3" json:"ID,omitempty"`
 }
 }
 
 
 func (m *LeaseRevokeRequest) Reset()         { *m = LeaseRevokeRequest{} }
 func (m *LeaseRevokeRequest) Reset()         { *m = LeaseRevokeRequest{} }
@@ -491,7 +491,7 @@ func (m *LeaseRevokeResponse) GetHeader() *ResponseHeader {
 }
 }
 
 
 type LeaseKeepAliveRequest struct {
 type LeaseKeepAliveRequest struct {
-	LeaseId int64 `protobuf:"varint,1,opt,name=lease_id,proto3" json:"lease_id,omitempty"`
+	ID int64 `protobuf:"varint,1,opt,proto3" json:"ID,omitempty"`
 }
 }
 
 
 func (m *LeaseKeepAliveRequest) Reset()         { *m = LeaseKeepAliveRequest{} }
 func (m *LeaseKeepAliveRequest) Reset()         { *m = LeaseKeepAliveRequest{} }
@@ -499,9 +499,8 @@ func (m *LeaseKeepAliveRequest) String() string { return proto.CompactTextString
 func (*LeaseKeepAliveRequest) ProtoMessage()    {}
 func (*LeaseKeepAliveRequest) ProtoMessage()    {}
 
 
 type LeaseKeepAliveResponse struct {
 type LeaseKeepAliveResponse struct {
-	Header  *ResponseHeader `protobuf:"bytes,1,opt,name=header" json:"header,omitempty"`
-	LeaseId int64           `protobuf:"varint,2,opt,name=lease_id,proto3" json:"lease_id,omitempty"`
-	Ttl     int64           `protobuf:"varint,3,opt,name=ttl,proto3" json:"ttl,omitempty"`
+	Header *ResponseHeader `protobuf:"bytes,1,opt,name=header" json:"header,omitempty"`
+	TTL    int64           `protobuf:"varint,2,opt,proto3" json:"TTL,omitempty"`
 }
 }
 
 
 func (m *LeaseKeepAliveResponse) Reset()         { *m = LeaseKeepAliveResponse{} }
 func (m *LeaseKeepAliveResponse) Reset()         { *m = LeaseKeepAliveResponse{} }
@@ -1741,10 +1740,10 @@ func (m *LeaseCreateRequest) MarshalTo(data []byte) (int, error) {
 	_ = i
 	_ = i
 	var l int
 	var l int
 	_ = l
 	_ = l
-	if m.Ttl != 0 {
+	if m.TTL != 0 {
 		data[i] = 0x8
 		data[i] = 0x8
 		i++
 		i++
-		i = encodeVarintRpc(data, i, uint64(m.Ttl))
+		i = encodeVarintRpc(data, i, uint64(m.TTL))
 	}
 	}
 	return i, nil
 	return i, nil
 }
 }
@@ -1774,15 +1773,15 @@ func (m *LeaseCreateResponse) MarshalTo(data []byte) (int, error) {
 		}
 		}
 		i += n15
 		i += n15
 	}
 	}
-	if m.LeaseId != 0 {
+	if m.ID != 0 {
 		data[i] = 0x10
 		data[i] = 0x10
 		i++
 		i++
-		i = encodeVarintRpc(data, i, uint64(m.LeaseId))
+		i = encodeVarintRpc(data, i, uint64(m.ID))
 	}
 	}
-	if m.Ttl != 0 {
+	if m.TTL != 0 {
 		data[i] = 0x18
 		data[i] = 0x18
 		i++
 		i++
-		i = encodeVarintRpc(data, i, uint64(m.Ttl))
+		i = encodeVarintRpc(data, i, uint64(m.TTL))
 	}
 	}
 	if len(m.Error) > 0 {
 	if len(m.Error) > 0 {
 		data[i] = 0x22
 		data[i] = 0x22
@@ -1808,10 +1807,10 @@ func (m *LeaseRevokeRequest) MarshalTo(data []byte) (int, error) {
 	_ = i
 	_ = i
 	var l int
 	var l int
 	_ = l
 	_ = l
-	if m.LeaseId != 0 {
+	if m.ID != 0 {
 		data[i] = 0x8
 		data[i] = 0x8
 		i++
 		i++
-		i = encodeVarintRpc(data, i, uint64(m.LeaseId))
+		i = encodeVarintRpc(data, i, uint64(m.ID))
 	}
 	}
 	return i, nil
 	return i, nil
 }
 }
@@ -1859,10 +1858,10 @@ func (m *LeaseKeepAliveRequest) MarshalTo(data []byte) (int, error) {
 	_ = i
 	_ = i
 	var l int
 	var l int
 	_ = l
 	_ = l
-	if m.LeaseId != 0 {
+	if m.ID != 0 {
 		data[i] = 0x8
 		data[i] = 0x8
 		i++
 		i++
-		i = encodeVarintRpc(data, i, uint64(m.LeaseId))
+		i = encodeVarintRpc(data, i, uint64(m.ID))
 	}
 	}
 	return i, nil
 	return i, nil
 }
 }
@@ -1892,15 +1891,10 @@ func (m *LeaseKeepAliveResponse) MarshalTo(data []byte) (int, error) {
 		}
 		}
 		i += n17
 		i += n17
 	}
 	}
-	if m.LeaseId != 0 {
+	if m.TTL != 0 {
 		data[i] = 0x10
 		data[i] = 0x10
 		i++
 		i++
-		i = encodeVarintRpc(data, i, uint64(m.LeaseId))
-	}
-	if m.Ttl != 0 {
-		data[i] = 0x18
-		i++
-		i = encodeVarintRpc(data, i, uint64(m.Ttl))
+		i = encodeVarintRpc(data, i, uint64(m.TTL))
 	}
 	}
 	return i, nil
 	return i, nil
 }
 }
@@ -2258,8 +2252,8 @@ func (m *WatchResponse) Size() (n int) {
 func (m *LeaseCreateRequest) Size() (n int) {
 func (m *LeaseCreateRequest) Size() (n int) {
 	var l int
 	var l int
 	_ = l
 	_ = l
-	if m.Ttl != 0 {
-		n += 1 + sovRpc(uint64(m.Ttl))
+	if m.TTL != 0 {
+		n += 1 + sovRpc(uint64(m.TTL))
 	}
 	}
 	return n
 	return n
 }
 }
@@ -2271,11 +2265,11 @@ func (m *LeaseCreateResponse) Size() (n int) {
 		l = m.Header.Size()
 		l = m.Header.Size()
 		n += 1 + l + sovRpc(uint64(l))
 		n += 1 + l + sovRpc(uint64(l))
 	}
 	}
-	if m.LeaseId != 0 {
-		n += 1 + sovRpc(uint64(m.LeaseId))
+	if m.ID != 0 {
+		n += 1 + sovRpc(uint64(m.ID))
 	}
 	}
-	if m.Ttl != 0 {
-		n += 1 + sovRpc(uint64(m.Ttl))
+	if m.TTL != 0 {
+		n += 1 + sovRpc(uint64(m.TTL))
 	}
 	}
 	l = len(m.Error)
 	l = len(m.Error)
 	if l > 0 {
 	if l > 0 {
@@ -2287,8 +2281,8 @@ func (m *LeaseCreateResponse) Size() (n int) {
 func (m *LeaseRevokeRequest) Size() (n int) {
 func (m *LeaseRevokeRequest) Size() (n int) {
 	var l int
 	var l int
 	_ = l
 	_ = l
-	if m.LeaseId != 0 {
-		n += 1 + sovRpc(uint64(m.LeaseId))
+	if m.ID != 0 {
+		n += 1 + sovRpc(uint64(m.ID))
 	}
 	}
 	return n
 	return n
 }
 }
@@ -2306,8 +2300,8 @@ func (m *LeaseRevokeResponse) Size() (n int) {
 func (m *LeaseKeepAliveRequest) Size() (n int) {
 func (m *LeaseKeepAliveRequest) Size() (n int) {
 	var l int
 	var l int
 	_ = l
 	_ = l
-	if m.LeaseId != 0 {
-		n += 1 + sovRpc(uint64(m.LeaseId))
+	if m.ID != 0 {
+		n += 1 + sovRpc(uint64(m.ID))
 	}
 	}
 	return n
 	return n
 }
 }
@@ -2319,11 +2313,8 @@ func (m *LeaseKeepAliveResponse) Size() (n int) {
 		l = m.Header.Size()
 		l = m.Header.Size()
 		n += 1 + l + sovRpc(uint64(l))
 		n += 1 + l + sovRpc(uint64(l))
 	}
 	}
-	if m.LeaseId != 0 {
-		n += 1 + sovRpc(uint64(m.LeaseId))
-	}
-	if m.Ttl != 0 {
-		n += 1 + sovRpc(uint64(m.Ttl))
+	if m.TTL != 0 {
+		n += 1 + sovRpc(uint64(m.TTL))
 	}
 	}
 	return n
 	return n
 }
 }
@@ -4351,16 +4342,16 @@ func (m *LeaseCreateRequest) Unmarshal(data []byte) error {
 		switch fieldNum {
 		switch fieldNum {
 		case 1:
 		case 1:
 			if wireType != 0 {
 			if wireType != 0 {
-				return fmt.Errorf("proto: wrong wireType = %d for field Ttl", wireType)
+				return fmt.Errorf("proto: wrong wireType = %d for field TTL", wireType)
 			}
 			}
-			m.Ttl = 0
+			m.TTL = 0
 			for shift := uint(0); ; shift += 7 {
 			for shift := uint(0); ; shift += 7 {
 				if iNdEx >= l {
 				if iNdEx >= l {
 					return io.ErrUnexpectedEOF
 					return io.ErrUnexpectedEOF
 				}
 				}
 				b := data[iNdEx]
 				b := data[iNdEx]
 				iNdEx++
 				iNdEx++
-				m.Ttl |= (int64(b) & 0x7F) << shift
+				m.TTL |= (int64(b) & 0x7F) << shift
 				if b < 0x80 {
 				if b < 0x80 {
 					break
 					break
 				}
 				}
@@ -4442,32 +4433,32 @@ func (m *LeaseCreateResponse) Unmarshal(data []byte) error {
 			iNdEx = postIndex
 			iNdEx = postIndex
 		case 2:
 		case 2:
 			if wireType != 0 {
 			if wireType != 0 {
-				return fmt.Errorf("proto: wrong wireType = %d for field LeaseId", wireType)
+				return fmt.Errorf("proto: wrong wireType = %d for field ID", wireType)
 			}
 			}
-			m.LeaseId = 0
+			m.ID = 0
 			for shift := uint(0); ; shift += 7 {
 			for shift := uint(0); ; shift += 7 {
 				if iNdEx >= l {
 				if iNdEx >= l {
 					return io.ErrUnexpectedEOF
 					return io.ErrUnexpectedEOF
 				}
 				}
 				b := data[iNdEx]
 				b := data[iNdEx]
 				iNdEx++
 				iNdEx++
-				m.LeaseId |= (int64(b) & 0x7F) << shift
+				m.ID |= (int64(b) & 0x7F) << shift
 				if b < 0x80 {
 				if b < 0x80 {
 					break
 					break
 				}
 				}
 			}
 			}
 		case 3:
 		case 3:
 			if wireType != 0 {
 			if wireType != 0 {
-				return fmt.Errorf("proto: wrong wireType = %d for field Ttl", wireType)
+				return fmt.Errorf("proto: wrong wireType = %d for field TTL", wireType)
 			}
 			}
-			m.Ttl = 0
+			m.TTL = 0
 			for shift := uint(0); ; shift += 7 {
 			for shift := uint(0); ; shift += 7 {
 				if iNdEx >= l {
 				if iNdEx >= l {
 					return io.ErrUnexpectedEOF
 					return io.ErrUnexpectedEOF
 				}
 				}
 				b := data[iNdEx]
 				b := data[iNdEx]
 				iNdEx++
 				iNdEx++
-				m.Ttl |= (int64(b) & 0x7F) << shift
+				m.TTL |= (int64(b) & 0x7F) << shift
 				if b < 0x80 {
 				if b < 0x80 {
 					break
 					break
 				}
 				}
@@ -4545,16 +4536,16 @@ func (m *LeaseRevokeRequest) Unmarshal(data []byte) error {
 		switch fieldNum {
 		switch fieldNum {
 		case 1:
 		case 1:
 			if wireType != 0 {
 			if wireType != 0 {
-				return fmt.Errorf("proto: wrong wireType = %d for field LeaseId", wireType)
+				return fmt.Errorf("proto: wrong wireType = %d for field ID", wireType)
 			}
 			}
-			m.LeaseId = 0
+			m.ID = 0
 			for shift := uint(0); ; shift += 7 {
 			for shift := uint(0); ; shift += 7 {
 				if iNdEx >= l {
 				if iNdEx >= l {
 					return io.ErrUnexpectedEOF
 					return io.ErrUnexpectedEOF
 				}
 				}
 				b := data[iNdEx]
 				b := data[iNdEx]
 				iNdEx++
 				iNdEx++
-				m.LeaseId |= (int64(b) & 0x7F) << shift
+				m.ID |= (int64(b) & 0x7F) << shift
 				if b < 0x80 {
 				if b < 0x80 {
 					break
 					break
 				}
 				}
@@ -4681,16 +4672,16 @@ func (m *LeaseKeepAliveRequest) Unmarshal(data []byte) error {
 		switch fieldNum {
 		switch fieldNum {
 		case 1:
 		case 1:
 			if wireType != 0 {
 			if wireType != 0 {
-				return fmt.Errorf("proto: wrong wireType = %d for field LeaseId", wireType)
+				return fmt.Errorf("proto: wrong wireType = %d for field ID", wireType)
 			}
 			}
-			m.LeaseId = 0
+			m.ID = 0
 			for shift := uint(0); ; shift += 7 {
 			for shift := uint(0); ; shift += 7 {
 				if iNdEx >= l {
 				if iNdEx >= l {
 					return io.ErrUnexpectedEOF
 					return io.ErrUnexpectedEOF
 				}
 				}
 				b := data[iNdEx]
 				b := data[iNdEx]
 				iNdEx++
 				iNdEx++
-				m.LeaseId |= (int64(b) & 0x7F) << shift
+				m.ID |= (int64(b) & 0x7F) << shift
 				if b < 0x80 {
 				if b < 0x80 {
 					break
 					break
 				}
 				}
@@ -4772,32 +4763,16 @@ func (m *LeaseKeepAliveResponse) Unmarshal(data []byte) error {
 			iNdEx = postIndex
 			iNdEx = postIndex
 		case 2:
 		case 2:
 			if wireType != 0 {
 			if wireType != 0 {
-				return fmt.Errorf("proto: wrong wireType = %d for field LeaseId", wireType)
-			}
-			m.LeaseId = 0
-			for shift := uint(0); ; shift += 7 {
-				if iNdEx >= l {
-					return io.ErrUnexpectedEOF
-				}
-				b := data[iNdEx]
-				iNdEx++
-				m.LeaseId |= (int64(b) & 0x7F) << shift
-				if b < 0x80 {
-					break
-				}
-			}
-		case 3:
-			if wireType != 0 {
-				return fmt.Errorf("proto: wrong wireType = %d for field Ttl", wireType)
+				return fmt.Errorf("proto: wrong wireType = %d for field TTL", wireType)
 			}
 			}
-			m.Ttl = 0
+			m.TTL = 0
 			for shift := uint(0); ; shift += 7 {
 			for shift := uint(0); ; shift += 7 {
 				if iNdEx >= l {
 				if iNdEx >= l {
 					return io.ErrUnexpectedEOF
 					return io.ErrUnexpectedEOF
 				}
 				}
 				b := data[iNdEx]
 				b := data[iNdEx]
 				iNdEx++
 				iNdEx++
-				m.Ttl |= (int64(b) & 0x7F) << shift
+				m.TTL |= (int64(b) & 0x7F) << shift
 				if b < 0x80 {
 				if b < 0x80 {
 					break
 					break
 				}
 				}

+ 6 - 7
etcdserver/etcdserverpb/rpc.proto

@@ -243,19 +243,19 @@ message WatchResponse {
 
 
 message LeaseCreateRequest {
 message LeaseCreateRequest {
   // advisory ttl in seconds
   // advisory ttl in seconds
-  int64 ttl = 1;
+  int64 TTL = 1;
 }
 }
 
 
 message LeaseCreateResponse {
 message LeaseCreateResponse {
   ResponseHeader header = 1;
   ResponseHeader header = 1;
-  int64 lease_id = 2;
+  int64 ID = 2;
   // server decided ttl in second
   // server decided ttl in second
-  int64 ttl = 3;
+  int64 TTL = 3;
   string error = 4;
   string error = 4;
 }
 }
 
 
 message LeaseRevokeRequest {
 message LeaseRevokeRequest {
-  int64 lease_id = 1;
+  int64 ID = 1;
 }
 }
 
 
 message LeaseRevokeResponse {
 message LeaseRevokeResponse {
@@ -263,11 +263,10 @@ message LeaseRevokeResponse {
 }
 }
 
 
 message LeaseKeepAliveRequest {
 message LeaseKeepAliveRequest {
-  int64 lease_id = 1;
+  int64 ID = 1;
 }
 }
 
 
 message LeaseKeepAliveResponse {
 message LeaseKeepAliveResponse {
   ResponseHeader header = 1;
   ResponseHeader header = 1;
-  int64 lease_id = 2;
-  int64 ttl = 3;
+  int64 TTL = 2;
 }
 }

+ 7 - 2
etcdserver/server.go

@@ -34,6 +34,7 @@ import (
 	"github.com/coreos/etcd/etcdserver/etcdhttp/httptypes"
 	"github.com/coreos/etcd/etcdserver/etcdhttp/httptypes"
 	pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
 	pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
 	"github.com/coreos/etcd/etcdserver/stats"
 	"github.com/coreos/etcd/etcdserver/stats"
+	"github.com/coreos/etcd/lease"
 	"github.com/coreos/etcd/pkg/fileutil"
 	"github.com/coreos/etcd/pkg/fileutil"
 	"github.com/coreos/etcd/pkg/idutil"
 	"github.com/coreos/etcd/pkg/idutil"
 	"github.com/coreos/etcd/pkg/pbutil"
 	"github.com/coreos/etcd/pkg/pbutil"
@@ -166,8 +167,9 @@ type EtcdServer struct {
 
 
 	store store.Store
 	store store.Store
 
 
-	kv dstorage.ConsistentWatchableKV
-	be backend.Backend
+	kv     dstorage.ConsistentWatchableKV
+	lessor lease.Lessor
+	be     backend.Backend
 
 
 	stats  *stats.ServerStats
 	stats  *stats.ServerStats
 	lstats *stats.LeaderStats
 	lstats *stats.LeaderStats
@@ -360,6 +362,7 @@ func NewServer(cfg *ServerConfig) (*EtcdServer, error) {
 	if cfg.V3demo {
 	if cfg.V3demo {
 		srv.be = backend.NewDefaultBackend(path.Join(cfg.SnapDir(), databaseFilename))
 		srv.be = backend.NewDefaultBackend(path.Join(cfg.SnapDir(), databaseFilename))
 		srv.kv = dstorage.New(srv.be, &srv.consistIndex)
 		srv.kv = dstorage.New(srv.be, &srv.consistIndex)
+		srv.lessor = lease.NewLessor(uint8(id), srv.be, srv.kv)
 	}
 	}
 
 
 	// TODO: move transport initialization near the definition of remote
 	// TODO: move transport initialization near the definition of remote
@@ -589,6 +592,8 @@ func (s *EtcdServer) applySnapshot(ep *etcdProgress, apply *apply) {
 			plog.Panicf("rename snapshot file error: %v", err)
 			plog.Panicf("rename snapshot file error: %v", err)
 		}
 		}
 
 
+		// TODO: recover leassor
+
 		newbe := backend.NewDefaultBackend(fn)
 		newbe := backend.NewDefaultBackend(fn)
 		if err := s.kv.Restore(newbe); err != nil {
 		if err := s.kv.Restore(newbe); err != nil {
 			plog.Panicf("restore KV error: %v", err)
 			plog.Panicf("restore KV error: %v", err)

+ 38 - 0
etcdserver/v3demo_server.go

@@ -34,6 +34,11 @@ type RaftKV interface {
 	Compact(ctx context.Context, r *pb.CompactionRequest) (*pb.CompactionResponse, error)
 	Compact(ctx context.Context, r *pb.CompactionRequest) (*pb.CompactionResponse, error)
 }
 }
 
 
+type Lessor interface {
+	LeaseCreate(ctx context.Context, r *pb.LeaseCreateRequest) (*pb.LeaseCreateResponse, error)
+	LeaseRevoke(ctx context.Context, r *pb.LeaseRevokeRequest) (*pb.LeaseRevokeResponse, error)
+}
+
 func (s *EtcdServer) Range(ctx context.Context, r *pb.RangeRequest) (*pb.RangeResponse, error) {
 func (s *EtcdServer) Range(ctx context.Context, r *pb.RangeRequest) (*pb.RangeResponse, error) {
 	result, err := s.processInternalRaftRequest(ctx, pb.InternalRaftRequest{Range: r})
 	result, err := s.processInternalRaftRequest(ctx, pb.InternalRaftRequest{Range: r})
 	if err != nil {
 	if err != nil {
@@ -74,6 +79,22 @@ func (s *EtcdServer) Compact(ctx context.Context, r *pb.CompactionRequest) (*pb.
 	return result.resp.(*pb.CompactionResponse), result.err
 	return result.resp.(*pb.CompactionResponse), result.err
 }
 }
 
 
+func (s *EtcdServer) LeaseCreate(ctx context.Context, r *pb.LeaseCreateRequest) (*pb.LeaseCreateResponse, error) {
+	result, err := s.processInternalRaftRequest(ctx, pb.InternalRaftRequest{LeaseCreate: r})
+	if err != nil {
+		return nil, err
+	}
+	return result.resp.(*pb.LeaseCreateResponse), result.err
+}
+
+func (s *EtcdServer) LeaseRevoke(ctx context.Context, r *pb.LeaseRevokeRequest) (*pb.LeaseRevokeResponse, error) {
+	result, err := s.processInternalRaftRequest(ctx, pb.InternalRaftRequest{LeaseRevoke: r})
+	if err != nil {
+		return nil, err
+	}
+	return result.resp.(*pb.LeaseRevokeResponse), result.err
+}
+
 type applyResult struct {
 type applyResult struct {
 	resp proto.Message
 	resp proto.Message
 	err  error
 	err  error
@@ -115,6 +136,7 @@ const (
 
 
 func (s *EtcdServer) applyV3Request(r *pb.InternalRaftRequest) interface{} {
 func (s *EtcdServer) applyV3Request(r *pb.InternalRaftRequest) interface{} {
 	kv := s.getKV()
 	kv := s.getKV()
+	le := s.lessor
 
 
 	ar := &applyResult{}
 	ar := &applyResult{}
 
 
@@ -129,6 +151,10 @@ func (s *EtcdServer) applyV3Request(r *pb.InternalRaftRequest) interface{} {
 		ar.resp, ar.err = applyTxn(kv, r.Txn)
 		ar.resp, ar.err = applyTxn(kv, r.Txn)
 	case r.Compaction != nil:
 	case r.Compaction != nil:
 		ar.resp, ar.err = applyCompaction(kv, r.Compaction)
 		ar.resp, ar.err = applyCompaction(kv, r.Compaction)
+	case r.LeaseCreate != nil:
+		ar.resp, ar.err = applyLeaseCreate(le, r.LeaseCreate)
+	case r.LeaseRevoke != nil:
+		ar.resp, ar.err = applyLeaseRevoke(le, r.LeaseRevoke)
 	default:
 	default:
 		panic("not implemented")
 		panic("not implemented")
 	}
 	}
@@ -348,6 +374,18 @@ func applyCompare(txnID int64, kv dstorage.KV, c *pb.Compare) (int64, bool) {
 	return rev, true
 	return rev, true
 }
 }
 
 
+func applyLeaseCreate(le lease.Lessor, lc *pb.LeaseCreateRequest) (*pb.LeaseCreateResponse, error) {
+	l := le.Grant(lc.TTL)
+
+	return &pb.LeaseCreateResponse{ID: int64(l.ID), TTL: l.TTL}, nil
+}
+
+func applyLeaseRevoke(le lease.Lessor, lc *pb.LeaseRevokeRequest) (*pb.LeaseRevokeResponse, error) {
+	err := le.Revoke(lease.LeaseID(lc.ID))
+
+	return &pb.LeaseRevokeResponse{}, err
+}
+
 func compareInt64(a, b int64) int {
 func compareInt64(a, b int64) int {
 	switch {
 	switch {
 	case a < b:
 	case a < b:

+ 57 - 29
lease/lessor.go

@@ -43,8 +43,17 @@ type DeleteableRange interface {
 	DeleteRange(key, end []byte) (int64, int64)
 	DeleteRange(key, end []byte) (int64, int64)
 }
 }
 
 
-// a lessor is the owner of leases. It can grant, revoke,
-// renew and modify leases for lessee.
+// A Lessor is the owner of leases. It can grant, revoke, renew and modify leases for lessee.
+type Lessor interface {
+	// Grant grants a lease that expires at least after TTL seconds.
+	Grant(ttl int64) *Lease
+	// Revoke revokes a lease with given ID. The item attached to the
+	// given lease will be removed. If the ID does not exist, an error
+	// will be returned.
+	Revoke(id LeaseID) error
+}
+
+// lessor implements Lessor interface.
 // TODO: use clockwork for testability.
 // TODO: use clockwork for testability.
 type lessor struct {
 type lessor struct {
 	mu sync.Mutex
 	mu sync.Mutex
@@ -54,7 +63,7 @@ type lessor struct {
 	// We want to make Grant, Revoke, and FindExpired all O(logN) and
 	// We want to make Grant, Revoke, and FindExpired all O(logN) and
 	// Renew O(1).
 	// Renew O(1).
 	// FindExpired and Renew should be the most frequent operations.
 	// FindExpired and Renew should be the most frequent operations.
-	leaseMap map[LeaseID]*lease
+	leaseMap map[LeaseID]*Lease
 
 
 	// A DeleteableRange the lessor operates on.
 	// A DeleteableRange the lessor operates on.
 	// When a lease expires, the lessor will delete the
 	// When a lease expires, the lessor will delete the
@@ -68,9 +77,19 @@ type lessor struct {
 	idgen *idutil.Generator
 	idgen *idutil.Generator
 }
 }
 
 
-func NewLessor(lessorID uint8, b backend.Backend, dr DeleteableRange) *lessor {
+func NewLessor(lessorID uint8, b backend.Backend, dr DeleteableRange) Lessor {
+	return newLessor(lessorID, b, dr)
+}
+
+func newLessor(lessorID uint8, b backend.Backend, dr DeleteableRange) *lessor {
+	// ensure the most significant bit of lessorID is 0.
+	// so all the IDs generated by id generator will be greater than 0.
+	if int8(lessorID) < 0 {
+		lessorID = uint8(-int8(lessorID))
+	}
+
 	l := &lessor{
 	l := &lessor{
-		leaseMap: make(map[LeaseID]*lease),
+		leaseMap: make(map[LeaseID]*Lease),
 		b:        b,
 		b:        b,
 		dr:       dr,
 		dr:       dr,
 		idgen:    idutil.NewGenerator(lessorID, time.Now()),
 		idgen:    idutil.NewGenerator(lessorID, time.Now()),
@@ -80,10 +99,9 @@ func NewLessor(lessorID uint8, b backend.Backend, dr DeleteableRange) *lessor {
 	return l
 	return l
 }
 }
 
 
-// Grant grants a lease that expires at least after TTL seconds.
 // TODO: when lessor is under high load, it should give out lease
 // TODO: when lessor is under high load, it should give out lease
 // with longer TTL to reduce renew load.
 // with longer TTL to reduce renew load.
-func (le *lessor) Grant(ttl int64) *lease {
+func (le *lessor) Grant(ttl int64) *Lease {
 	// TODO: define max TTL
 	// TODO: define max TTL
 	expiry := time.Now().Add(time.Duration(ttl) * time.Second)
 	expiry := time.Now().Add(time.Duration(ttl) * time.Second)
 	expiry = minExpiry(time.Now(), expiry)
 	expiry = minExpiry(time.Now(), expiry)
@@ -93,7 +111,7 @@ func (le *lessor) Grant(ttl int64) *lease {
 	le.mu.Lock()
 	le.mu.Lock()
 	defer le.mu.Unlock()
 	defer le.mu.Unlock()
 
 
-	l := &lease{id: id, ttl: ttl, expiry: expiry, itemSet: make(map[leaseItem]struct{})}
+	l := &Lease{ID: id, TTL: ttl, expiry: expiry, itemSet: make(map[leaseItem]struct{})}
 	if _, ok := le.leaseMap[id]; ok {
 	if _, ok := le.leaseMap[id]; ok {
 		panic("lease: unexpected duplicate ID!")
 		panic("lease: unexpected duplicate ID!")
 	}
 	}
@@ -104,9 +122,6 @@ func (le *lessor) Grant(ttl int64) *lease {
 	return l
 	return l
 }
 }
 
 
-// Revoke revokes a lease with given ID. The item attached to the
-// given lease will be removed. If the ID does not exist, an error
-// will be returned.
 func (le *lessor) Revoke(id LeaseID) error {
 func (le *lessor) Revoke(id LeaseID) error {
 	le.mu.Lock()
 	le.mu.Lock()
 	defer le.mu.Unlock()
 	defer le.mu.Unlock()
@@ -120,7 +135,7 @@ func (le *lessor) Revoke(id LeaseID) error {
 		le.dr.DeleteRange([]byte(item.key), nil)
 		le.dr.DeleteRange([]byte(item.key), nil)
 	}
 	}
 
 
-	delete(le.leaseMap, l.id)
+	delete(le.leaseMap, l.ID)
 	l.removeFrom(le.b)
 	l.removeFrom(le.b)
 
 
 	return nil
 	return nil
@@ -138,7 +153,7 @@ func (le *lessor) Renew(id LeaseID) error {
 		return fmt.Errorf("lease: cannot find lease %x", id)
 		return fmt.Errorf("lease: cannot find lease %x", id)
 	}
 	}
 
 
-	expiry := time.Now().Add(time.Duration(l.ttl) * time.Second)
+	expiry := time.Now().Add(time.Duration(l.TTL) * time.Second)
 	l.expiry = minExpiry(time.Now(), expiry)
 	l.expiry = minExpiry(time.Now(), expiry)
 	return nil
 	return nil
 }
 }
@@ -161,13 +176,24 @@ func (le *lessor) Attach(id LeaseID, items []leaseItem) error {
 	return nil
 	return nil
 }
 }
 
 
+func (le *lessor) Recover(b backend.Backend, dr DeleteableRange) {
+	le.mu.Lock()
+	defer le.mu.Unlock()
+
+	le.b = b
+	le.dr = dr
+	le.leaseMap = make(map[LeaseID]*Lease)
+
+	le.initAndRecover()
+}
+
 // findExpiredLeases loops all the leases in the leaseMap and returns the expired
 // findExpiredLeases loops all the leases in the leaseMap and returns the expired
 // leases that needed to be revoked.
 // leases that needed to be revoked.
-func (le *lessor) findExpiredLeases() []*lease {
+func (le *lessor) findExpiredLeases() []*Lease {
 	le.mu.Lock()
 	le.mu.Lock()
 	defer le.mu.Unlock()
 	defer le.mu.Unlock()
 
 
-	leases := make([]*lease, 0, 16)
+	leases := make([]*Lease, 0, 16)
 	now := time.Now()
 	now := time.Now()
 
 
 	for _, l := range le.leaseMap {
 	for _, l := range le.leaseMap {
@@ -181,7 +207,7 @@ func (le *lessor) findExpiredLeases() []*lease {
 
 
 // get gets the lease with given id.
 // get gets the lease with given id.
 // get is a helper fucntion for testing, at least for now.
 // get is a helper fucntion for testing, at least for now.
-func (le *lessor) get(id LeaseID) *lease {
+func (le *lessor) get(id LeaseID) *Lease {
 	le.mu.Lock()
 	le.mu.Lock()
 	defer le.mu.Unlock()
 	defer le.mu.Unlock()
 
 
@@ -191,7 +217,6 @@ func (le *lessor) get(id LeaseID) *lease {
 func (le *lessor) initAndRecover() {
 func (le *lessor) initAndRecover() {
 	tx := le.b.BatchTx()
 	tx := le.b.BatchTx()
 	tx.Lock()
 	tx.Lock()
-	defer tx.Unlock()
 
 
 	tx.UnsafeCreateBucket(leaseBucketName)
 	tx.UnsafeCreateBucket(leaseBucketName)
 	_, vs := tx.UnsafeRange(leaseBucketName, int64ToBytes(0), int64ToBytes(math.MaxInt64), 0)
 	_, vs := tx.UnsafeRange(leaseBucketName, int64ToBytes(0), int64ToBytes(math.MaxInt64), 0)
@@ -200,33 +225,36 @@ func (le *lessor) initAndRecover() {
 		var lpb leasepb.Lease
 		var lpb leasepb.Lease
 		err := lpb.Unmarshal(vs[i])
 		err := lpb.Unmarshal(vs[i])
 		if err != nil {
 		if err != nil {
+			tx.Unlock()
 			panic("failed to unmarshal lease proto item")
 			panic("failed to unmarshal lease proto item")
 		}
 		}
-		id := LeaseID(lpb.ID)
-		le.leaseMap[id] = &lease{
-			id:  id,
-			ttl: lpb.TTL,
+		ID := LeaseID(lpb.ID)
+		le.leaseMap[ID] = &Lease{
+			ID:  ID,
+			TTL: lpb.TTL,
 
 
 			// itemSet will be filled in when recover key-value pairs
 			// itemSet will be filled in when recover key-value pairs
 			expiry: minExpiry(time.Now(), time.Now().Add(time.Second*time.Duration(lpb.TTL))),
 			expiry: minExpiry(time.Now(), time.Now().Add(time.Second*time.Duration(lpb.TTL))),
 		}
 		}
 	}
 	}
+	tx.Unlock()
+
 	le.b.ForceCommit()
 	le.b.ForceCommit()
 }
 }
 
 
-type lease struct {
-	id  LeaseID
-	ttl int64 // time to live in seconds
+type Lease struct {
+	ID  LeaseID
+	TTL int64 // time to live in seconds
 
 
 	itemSet map[leaseItem]struct{}
 	itemSet map[leaseItem]struct{}
 	// expiry time in unixnano
 	// expiry time in unixnano
 	expiry time.Time
 	expiry time.Time
 }
 }
 
 
-func (l lease) persistTo(b backend.Backend) {
-	key := int64ToBytes(int64(l.id))
+func (l Lease) persistTo(b backend.Backend) {
+	key := int64ToBytes(int64(l.ID))
 
 
-	lpb := leasepb.Lease{ID: int64(l.id), TTL: int64(l.ttl)}
+	lpb := leasepb.Lease{ID: int64(l.ID), TTL: int64(l.TTL)}
 	val, err := lpb.Marshal()
 	val, err := lpb.Marshal()
 	if err != nil {
 	if err != nil {
 		panic("failed to marshal lease proto item")
 		panic("failed to marshal lease proto item")
@@ -237,8 +265,8 @@ func (l lease) persistTo(b backend.Backend) {
 	b.BatchTx().Unlock()
 	b.BatchTx().Unlock()
 }
 }
 
 
-func (l lease) removeFrom(b backend.Backend) {
-	key := int64ToBytes(int64(l.id))
+func (l Lease) removeFrom(b backend.Backend) {
+	key := int64ToBytes(int64(l.ID))
 
 
 	b.BatchTx().Lock()
 	b.BatchTx().Lock()
 	b.BatchTx().UnsafeDelete(leaseBucketName, key)
 	b.BatchTx().UnsafeDelete(leaseBucketName, key)

+ 23 - 23
lease/lessor_test.go

@@ -33,10 +33,10 @@ func TestLessorGrant(t *testing.T) {
 	defer os.RemoveAll(dir)
 	defer os.RemoveAll(dir)
 	defer be.Close()
 	defer be.Close()
 
 
-	le := NewLessor(1, be, &fakeDeleteable{})
+	le := newLessor(1, be, &fakeDeleteable{})
 
 
 	l := le.Grant(1)
 	l := le.Grant(1)
-	gl := le.get(l.id)
+	gl := le.get(l.ID)
 
 
 	if !reflect.DeepEqual(gl, l) {
 	if !reflect.DeepEqual(gl, l) {
 		t.Errorf("lease = %v, want %v", gl, l)
 		t.Errorf("lease = %v, want %v", gl, l)
@@ -46,12 +46,12 @@ func TestLessorGrant(t *testing.T) {
 	}
 	}
 
 
 	nl := le.Grant(1)
 	nl := le.Grant(1)
-	if nl.id == l.id {
-		t.Errorf("new lease.id = %x, want != %x", nl.id, l.id)
+	if nl.ID == l.ID {
+		t.Errorf("new lease.id = %x, want != %x", nl.ID, l.ID)
 	}
 	}
 
 
 	be.BatchTx().Lock()
 	be.BatchTx().Lock()
-	_, vs := be.BatchTx().UnsafeRange(leaseBucketName, int64ToBytes(int64(l.id)), nil, 0)
+	_, vs := be.BatchTx().UnsafeRange(leaseBucketName, int64ToBytes(int64(l.ID)), nil, 0)
 	if len(vs) != 1 {
 	if len(vs) != 1 {
 		t.Errorf("len(vs) = %d, want 1", len(vs))
 		t.Errorf("len(vs) = %d, want 1", len(vs))
 	}
 	}
@@ -69,7 +69,7 @@ func TestLessorRevoke(t *testing.T) {
 
 
 	fd := &fakeDeleteable{}
 	fd := &fakeDeleteable{}
 
 
-	le := NewLessor(1, be, fd)
+	le := newLessor(1, be, fd)
 
 
 	// grant a lease with long term (100 seconds) to
 	// grant a lease with long term (100 seconds) to
 	// avoid early termination during the test.
 	// avoid early termination during the test.
@@ -80,18 +80,18 @@ func TestLessorRevoke(t *testing.T) {
 		{"bar"},
 		{"bar"},
 	}
 	}
 
 
-	err := le.Attach(l.id, items)
+	err := le.Attach(l.ID, items)
 	if err != nil {
 	if err != nil {
 		t.Fatalf("failed to attach items to the lease: %v", err)
 		t.Fatalf("failed to attach items to the lease: %v", err)
 	}
 	}
 
 
-	err = le.Revoke(l.id)
+	err = le.Revoke(l.ID)
 	if err != nil {
 	if err != nil {
 		t.Fatal("failed to revoke lease:", err)
 		t.Fatal("failed to revoke lease:", err)
 	}
 	}
 
 
-	if le.get(l.id) != nil {
-		t.Errorf("got revoked lease %x", l.id)
+	if le.get(l.ID) != nil {
+		t.Errorf("got revoked lease %x", l.ID)
 	}
 	}
 
 
 	wdeleted := []string{"foo_", "bar_"}
 	wdeleted := []string{"foo_", "bar_"}
@@ -100,7 +100,7 @@ func TestLessorRevoke(t *testing.T) {
 	}
 	}
 
 
 	be.BatchTx().Lock()
 	be.BatchTx().Lock()
-	_, vs := be.BatchTx().UnsafeRange(leaseBucketName, int64ToBytes(int64(l.id)), nil, 0)
+	_, vs := be.BatchTx().UnsafeRange(leaseBucketName, int64ToBytes(int64(l.ID)), nil, 0)
 	if len(vs) != 0 {
 	if len(vs) != 0 {
 		t.Errorf("len(vs) = %d, want 0", len(vs))
 		t.Errorf("len(vs) = %d, want 0", len(vs))
 	}
 	}
@@ -113,14 +113,14 @@ func TestLessorRenew(t *testing.T) {
 	defer be.Close()
 	defer be.Close()
 	defer os.RemoveAll(dir)
 	defer os.RemoveAll(dir)
 
 
-	le := NewLessor(1, be, &fakeDeleteable{})
+	le := newLessor(1, be, &fakeDeleteable{})
 	l := le.Grant(5)
 	l := le.Grant(5)
 
 
 	// manually change the ttl field
 	// manually change the ttl field
-	l.ttl = 10
+	l.TTL = 10
 
 
-	le.Renew(l.id)
-	l = le.get(l.id)
+	le.Renew(l.ID)
+	l = le.get(l.ID)
 
 
 	if l.expiry.Sub(time.Now()) < 9*time.Second {
 	if l.expiry.Sub(time.Now()) < 9*time.Second {
 		t.Errorf("failed to renew the lease")
 		t.Errorf("failed to renew the lease")
@@ -134,20 +134,20 @@ func TestLessorRecover(t *testing.T) {
 	defer os.RemoveAll(dir)
 	defer os.RemoveAll(dir)
 	defer be.Close()
 	defer be.Close()
 
 
-	le := NewLessor(1, be, &fakeDeleteable{})
+	le := newLessor(1, be, &fakeDeleteable{})
 	l1 := le.Grant(10)
 	l1 := le.Grant(10)
 	l2 := le.Grant(20)
 	l2 := le.Grant(20)
 
 
 	// Create a new lessor with the same backend
 	// Create a new lessor with the same backend
-	nle := NewLessor(1, be, &fakeDeleteable{})
-	nl1 := nle.get(l1.id)
-	if nl1 == nil || nl1.ttl != l1.ttl {
-		t.Errorf("nl1 = %v, want nl1.TTL= %d", l1.ttl)
+	nle := newLessor(1, be, &fakeDeleteable{})
+	nl1 := nle.get(l1.ID)
+	if nl1 == nil || nl1.TTL != l1.TTL {
+		t.Errorf("nl1 = %v, want nl1.TTL= %d", nl1.TTL, l1.TTL)
 	}
 	}
 
 
-	nl2 := nle.get(l2.id)
-	if nl2 == nil || nl2.ttl != l2.ttl {
-		t.Errorf("nl2 = %v, want nl2.TTL= %d", l2.ttl)
+	nl2 := nle.get(l2.ID)
+	if nl2 == nil || nl2.TTL != l2.TTL {
+		t.Errorf("nl2 = %v, want nl2.TTL= %d", nl2.TTL, l2.TTL)
 	}
 	}
 }
 }