Skip to content

Commit

Permalink
Merge pull request #6321 from gyuho/lease-information
Browse files Browse the repository at this point in the history
*: lease timetolive
  • Loading branch information
gyuho authored Sep 8, 2016
2 parents 33b3fdc + b7dc6cc commit 0b67584
Show file tree
Hide file tree
Showing 21 changed files with 1,723 additions and 368 deletions.
38 changes: 38 additions & 0 deletions Documentation/dev-guide/api_reference_v3.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ for grpc-gateway
| LeaseGrant | LeaseGrantRequest | LeaseGrantResponse | LeaseGrant creates a lease which expires if the server does not receive a keepAlive within a given time to live period. All keys attached to the lease will be expired and deleted if the lease expires. Each expired key generates a delete event in the event history. |
| LeaseRevoke | LeaseRevokeRequest | LeaseRevokeResponse | LeaseRevoke revokes a lease. All keys attached to the lease will expire and be deleted. |
| LeaseKeepAlive | LeaseKeepAliveRequest | LeaseKeepAliveResponse | LeaseKeepAlive keeps the lease alive by streaming keep alive requests from the client to the server and streaming keep alive responses from the server to the client. |
| LeaseTimeToLive | LeaseTimeToLiveRequest | LeaseTimeToLiveResponse | LeaseTimeToLive retrieves lease information. |



Expand Down Expand Up @@ -510,6 +511,27 @@ Empty field.



##### message `LeaseTimeToLiveRequest` (etcdserver/etcdserverpb/rpc.proto)

| Field | Description | Type |
| ----- | ----------- | ---- |
| ID | ID is the lease ID for the lease. | int64 |
| keys | keys is true to query all the keys attached to this lease. | bool |



##### message `LeaseTimeToLiveResponse` (etcdserver/etcdserverpb/rpc.proto)

| Field | Description | Type |
| ----- | ----------- | ---- |
| header | | ResponseHeader |
| ID | ID is the lease ID from the keep alive request. | int64 |
| TTL | TTL is the remaining TTL in seconds for the lease; the lease will expire in under TTL+1 seconds. | int64 |
| grantedTTL | GrantedTTL is the initial granted time in seconds upon lease creation/renewal. | int64 |
| keys | Keys is the list of keys attached to this lease. | (slice of) bytes |



##### message `Member` (etcdserver/etcdserverpb/rpc.proto)

| Field | Description | Type |
Expand Down Expand Up @@ -799,6 +821,22 @@ From google paxosdb paper: Our implementation hinges around a powerful primitive



##### message `LeaseInternalRequest` (lease/leasepb/lease.proto)

| Field | Description | Type |
| ----- | ----------- | ---- |
| LeaseTimeToLiveRequest | | etcdserverpb.LeaseTimeToLiveRequest |



##### message `LeaseInternalResponse` (lease/leasepb/lease.proto)

| Field | Description | Type |
| ----- | ----------- | ---- |
| LeaseTimeToLiveResponse | | etcdserverpb.LeaseTimeToLiveResponse |



##### message `Permission` (auth/authpb/auth.proto)

Permission is a single entity
Expand Down
73 changes: 73 additions & 0 deletions Documentation/dev-guide/apispec/swagger/rpc.swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -636,6 +636,33 @@
]
}
},
"/v3alpha/kv/lease/timetolive": {
"post": {
"summary": "LeaseTimeToLive retrieves lease information.",
"operationId": "LeaseTimeToLive",
"responses": {
"200": {
"description": "",
"schema": {
"$ref": "#/definitions/etcdserverpbLeaseTimeToLiveResponse"
}
}
},
"parameters": [
{
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/etcdserverpbLeaseTimeToLiveRequest"
}
}
],
"tags": [
"Lease"
]
}
},
"/v3alpha/kv/put": {
"post": {
"summary": "Put puts the given key into the key-value store.\nA put request increments the revision of the key-value store\nand generates one event in the event history.",
Expand Down Expand Up @@ -1614,6 +1641,52 @@
}
}
},
"etcdserverpbLeaseTimeToLiveRequest": {
"type": "object",
"properties": {
"ID": {
"type": "string",
"format": "int64",
"description": "ID is the lease ID for the lease."
},
"keys": {
"type": "boolean",
"format": "boolean",
"description": "keys is true to query all the keys attached to this lease."
}
}
},
"etcdserverpbLeaseTimeToLiveResponse": {
"type": "object",
"properties": {
"ID": {
"type": "string",
"format": "int64",
"description": "ID is the lease ID from the keep alive request."
},
"TTL": {
"type": "string",
"format": "int64",
"description": "TTL is the remaining TTL in seconds for the lease; the lease will expire in under TTL+1 seconds."
},
"grantedTTL": {
"type": "string",
"format": "int64",
"description": "GrantedTTL is the initial granted time in seconds upon lease creation/renewal."
},
"header": {
"$ref": "#/definitions/etcdserverpbResponseHeader"
},
"keys": {
"type": "array",
"items": {
"type": "string",
"format": "byte"
},
"description": "Keys is the list of keys attached to this lease."
}
}
},
"etcdserverpbMember": {
"type": "object",
"properties": {
Expand Down
55 changes: 55 additions & 0 deletions clientv3/integration/lease_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
package integration

import (
"reflect"
"sort"
"testing"
"time"

Expand Down Expand Up @@ -455,3 +457,56 @@ func TestLeaseKeepAliveTTLTimeout(t *testing.T) {

clus.Members[0].Restart(t)
}

func TestLeaseTimeToLive(t *testing.T) {
defer testutil.AfterTest(t)

clus := integration.NewClusterV3(t, &integration.ClusterConfig{Size: 3})
defer clus.Terminate(t)

lapi := clientv3.NewLease(clus.RandClient())
defer lapi.Close()

resp, err := lapi.Grant(context.Background(), 10)
if err != nil {
t.Errorf("failed to create lease %v", err)
}

kv := clientv3.NewKV(clus.RandClient())
keys := []string{"foo1", "foo2"}
for i := range keys {
if _, err = kv.Put(context.TODO(), keys[i], "bar", clientv3.WithLease(resp.ID)); err != nil {
t.Fatal(err)
}
}

lresp, lerr := lapi.TimeToLive(context.Background(), resp.ID, clientv3.WithAttachedKeys())
if lerr != nil {
t.Fatal(lerr)
}
if lresp.ID != resp.ID {
t.Fatalf("leaseID expected %d, got %d", resp.ID, lresp.ID)
}
if lresp.GrantedTTL != int64(10) {
t.Fatalf("GrantedTTL expected %d, got %d", 10, lresp.GrantedTTL)
}
if lresp.TTL == 0 || lresp.TTL > lresp.GrantedTTL {
t.Fatalf("unexpected TTL %d (granted %d)", lresp.TTL, lresp.GrantedTTL)
}
ks := make([]string, len(lresp.Keys))
for i := range lresp.Keys {
ks[i] = string(lresp.Keys[i])
}
sort.Strings(ks)
if !reflect.DeepEqual(ks, keys) {
t.Fatalf("keys expected %v, got %v", keys, ks)
}

lresp, lerr = lapi.TimeToLive(context.Background(), resp.ID)
if lerr != nil {
t.Fatal(lerr)
}
if len(lresp.Keys) != 0 {
t.Fatalf("unexpected keys %+v", lresp.Keys)
}
}
42 changes: 42 additions & 0 deletions clientv3/lease.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,21 @@ type LeaseKeepAliveResponse struct {
TTL int64
}

// LeaseTimeToLiveResponse is used to convert the protobuf lease timetolive response.
type LeaseTimeToLiveResponse struct {
*pb.ResponseHeader
ID LeaseID `json:"id"`

// TTL is the remaining TTL in seconds for the lease; the lease will expire in under TTL+1 seconds.
TTL int64 `json:"ttl"`

// GrantedTTL is the initial granted time in seconds upon lease creation/renewal.
GrantedTTL int64 `json:"granted-ttl"`

// Keys is the list of keys attached to this lease.
Keys [][]byte `json:"keys"`
}

const (
// defaultTTL is the assumed lease TTL used for the first keepalive
// deadline before the actual TTL is known to the client.
Expand All @@ -61,6 +76,9 @@ type Lease interface {
// Revoke revokes the given lease.
Revoke(ctx context.Context, id LeaseID) (*LeaseRevokeResponse, error)

// TimeToLive retrieves the lease information of the given lease ID.
TimeToLive(ctx context.Context, id LeaseID, opts ...LeaseOption) (*LeaseTimeToLiveResponse, error)

// KeepAlive keeps the given lease alive forever.
KeepAlive(ctx context.Context, id LeaseID) (<-chan *LeaseKeepAliveResponse, error)

Expand Down Expand Up @@ -170,6 +188,30 @@ func (l *lessor) Revoke(ctx context.Context, id LeaseID) (*LeaseRevokeResponse,
}
}

func (l *lessor) TimeToLive(ctx context.Context, id LeaseID, opts ...LeaseOption) (*LeaseTimeToLiveResponse, error) {
cctx, cancel := context.WithCancel(ctx)
done := cancelWhenStop(cancel, l.stopCtx.Done())
defer close(done)

for {
r := toLeaseTimeToLiveRequest(id, opts...)
resp, err := l.remote.LeaseTimeToLive(cctx, r)
if err == nil {
gresp := &LeaseTimeToLiveResponse{
ResponseHeader: resp.GetHeader(),
ID: LeaseID(resp.ID),
TTL: resp.TTL,
GrantedTTL: resp.GrantedTTL,
Keys: resp.Keys,
}
return gresp, nil
}
if isHaltErr(cctx, err) {
return nil, toErr(cctx, err)
}
}
}

func (l *lessor) KeepAlive(ctx context.Context, id LeaseID) (<-chan *LeaseKeepAliveResponse, error) {
ch := make(chan *LeaseKeepAliveResponse, leaseResponseChSize)

Expand Down
30 changes: 29 additions & 1 deletion clientv3/op.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,6 @@ func (op Op) toRequestOp() *pb.RequestOp {
return &pb.RequestOp{Request: &pb.RequestOp_RequestPut{RequestPut: r}}
case tDeleteRange:
r := &pb.DeleteRangeRequest{Key: op.key, RangeEnd: op.end, PrevKv: op.prevKV}

return &pb.RequestOp{Request: &pb.RequestOp_RequestDeleteRange{RequestDeleteRange: r}}
default:
panic("Unknown Op")
Expand Down Expand Up @@ -320,3 +319,32 @@ func WithPrevKV() OpOption {
op.prevKV = true
}
}

// LeaseOp represents an Operation that lease can execute.
type LeaseOp struct {
id LeaseID

// for TimeToLive
attachedKeys bool
}

// LeaseOption configures lease operations.
type LeaseOption func(*LeaseOp)

func (op *LeaseOp) applyOpts(opts []LeaseOption) {
for _, opt := range opts {
opt(op)
}
}

// WithAttachedKeys requests lease timetolive API to return
// attached keys of given lease ID.
func WithAttachedKeys() LeaseOption {
return func(op *LeaseOp) { op.attachedKeys = true }
}

func toLeaseTimeToLiveRequest(id LeaseID, opts ...LeaseOption) *pb.LeaseTimeToLiveRequest {
ret := &LeaseOp{id: id}
ret.applyOpts(opts)
return &pb.LeaseTimeToLiveRequest{ID: int64(id), Keys: ret.attachedKeys}
}
31 changes: 29 additions & 2 deletions e2e/ctl_v3_lease_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,35 @@ import (
"testing"
)

func TestCtlV3LeaseKeepAlive(t *testing.T) { testCtl(t, leaseTestKeepAlive) }
func TestCtlV3LeaseRevoke(t *testing.T) { testCtl(t, leaseTestRevoke) }
func TestCtlV3LeaseGrantTimeToLive(t *testing.T) { testCtl(t, leaseTestGrantTimeToLive) }
func TestCtlV3LeaseKeepAlive(t *testing.T) { testCtl(t, leaseTestKeepAlive) }
func TestCtlV3LeaseRevoke(t *testing.T) { testCtl(t, leaseTestRevoke) }

func leaseTestGrantTimeToLive(cx ctlCtx) {
id, err := ctlV3LeaseGrant(cx, 10)
if err != nil {
cx.t.Fatal(err)
}

cmdArgs := append(cx.PrefixArgs(), "lease", "timetolive", id, "--keys")
proc, err := spawnCmd(cmdArgs)
if err != nil {
cx.t.Fatal(err)
}
line, err := proc.Expect(" granted with TTL(")
if err != nil {
cx.t.Fatal(err)
}
if err = proc.Close(); err != nil {
cx.t.Fatal(err)
}
if !strings.Contains(line, ", attached keys") {
cx.t.Fatalf("expected 'attached keys', got %q", line)
}
if !strings.Contains(line, id) {
cx.t.Fatalf("expected leaseID %q, got %q", id, line)
}
}

func leaseTestKeepAlive(cx ctlCtx) {
// put with TTL 10 seconds and keep-alive
Expand Down
34 changes: 34 additions & 0 deletions etcdctl/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,40 @@ LEASE REVOKE destroys a given lease, deleting all attached keys.
lease 32695410dcc0ca06 revoked
```


### LEASE TIMETOLIVE \<leaseID\>

LEASE TIMETOLIVE retrieves the lease information with the given lease ID.

#### Return value

- On success, prints lease information.

- On failure, prints an error message and returns with a non-zero exit code.

#### Example

```bash
./etcdctl lease grant 500
lease 2d8257079fa1bc0c granted with TTL(500s)

./etcdctl put foo1 bar --lease=2d8257079fa1bc0c
./etcdctl put foo2 bar --lease=2d8257079fa1bc0c

./etcdctl lease timetolive 2d8257079fa1bc0c
lease 2d8257079fa1bc0c granted with TTL(500s), remaining(481s)

./etcdctl lease timetolive 2d8257079fa1bc0c --keys
lease 2d8257079fa1bc0c granted with TTL(500s), remaining(472s), attached keys([foo2 foo1])

./etcdctl lease timetolive 2d8257079fa1bc0c --write-out=json
{"cluster_id":17186838941855831277,"member_id":4845372305070271874,"revision":3,"raft_term":2,"id":3279279168933706764,"ttl":465,"granted-ttl":500,"keys":null}

./etcdctl lease timetolive 2d8257079fa1bc0c --write-out=json --keys
{"cluster_id":17186838941855831277,"member_id":4845372305070271874,"revision":3,"raft_term":2,"id":3279279168933706764,"ttl":459,"granted-ttl":500,"keys":["Zm9vMQ==","Zm9vMg=="]}
```


### LEASE KEEP-ALIVE \<leaseID\>

LEASE KEEP-ALIVE periodically refreshes a lease so it does not expire.
Expand Down
Loading

0 comments on commit 0b67584

Please sign in to comment.