From 72a1e5618b7f43f85cc4fb32bf04c2dbc737bcb8 Mon Sep 17 00:00:00 2001 From: Gyu-Ho Lee Date: Mon, 22 Feb 2016 16:39:05 -0800 Subject: [PATCH] clientv3: add more code examples --- clientv3/README.md | 2 +- clientv3/doc.go | 43 +++++++++ clientv3/example_cluster_test.go | 131 +++++++++++++++++++++++++++ clientv3/example_kv_test.go | 147 ++++++++++++++++++++++++++---- clientv3/example_lease_test.go | 151 +++++++++++++++++++++++++++++++ clientv3/example_test.go | 47 ++++++++++ clientv3/example_watch_test.go | 67 ++++++++++++++ 7 files changed, 571 insertions(+), 17 deletions(-) create mode 100644 clientv3/doc.go create mode 100644 clientv3/example_cluster_test.go create mode 100644 clientv3/example_lease_test.go create mode 100644 clientv3/example_test.go create mode 100644 clientv3/example_watch_test.go diff --git a/clientv3/README.md b/clientv3/README.md index 91bc5e3f5..f89d39346 100644 --- a/clientv3/README.md +++ b/clientv3/README.md @@ -27,7 +27,7 @@ defer cli.Close() etcd v3 uses [`gRPC`](http://www.grpc.io) for remote procedure calls. And `clientv3` uses [`grpc-go`](https://github.com/grpc/grpc-go) to connect to etcd. Make sure to close the client after using it. -If the client is not closed, the connection will cause leaky goroutines. To specify client request timeout, +If the client is not closed, the connection will have leaky goroutines. To specify client request timeout, pass `context.WithTimeout` to APIs: ```go diff --git a/clientv3/doc.go b/clientv3/doc.go new file mode 100644 index 000000000..805931df4 --- /dev/null +++ b/clientv3/doc.go @@ -0,0 +1,43 @@ +// 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. + +// clientv3 is the official Go etcd client for v3. +// +// Create client using `clientv3.New`: +// +// cli, err := clientv3.New(clientv3.Config{ +// Endpoints: []string{"localhost:12378", "localhost:22378", "localhost:32378"}, +// DialTimeout: 5 * time.Second, +// }) +// if err != nil { +// // handle error! +// } +// defer cli.Close() +// +// Make sure to close the client after using it. If the client is not closed, the +// connection will have leaky goroutines. +// +// To specify client request timeout, pass context.WithTimeout to APIs: +// +// ctx, cancel := context.WithTimeout(context.Background(), timeout) +// resp, err := kvc.Put(ctx, "sample_key", "sample_value") +// cancel() +// if err != nil { +// // handle error! +// } +// // use the response +// +// TODO: document error handling +// +package clientv3 diff --git a/clientv3/example_cluster_test.go b/clientv3/example_cluster_test.go new file mode 100644 index 000000000..04bcf990f --- /dev/null +++ b/clientv3/example_cluster_test.go @@ -0,0 +1,131 @@ +// 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 clientv3_test + +import ( + "fmt" + "log" + + "github.com/coreos/etcd/Godeps/_workspace/src/golang.org/x/net/context" + "github.com/coreos/etcd/clientv3" +) + +func ExampleCluster_memberList() { + cli, err := clientv3.New(clientv3.Config{ + Endpoints: endpoints, + DialTimeout: dialTimeout, + }) + if err != nil { + log.Fatal(err) + } + defer cli.Close() + + capi := clientv3.NewCluster(cli) + + resp, err := capi.MemberList(context.Background()) + if err != nil { + log.Fatal(err) + } + fmt.Println("members:", len(resp.Members)) + // members: 3 +} + +func ExampleCluster_memberLeader() { + cli, err := clientv3.New(clientv3.Config{ + Endpoints: endpoints, + DialTimeout: dialTimeout, + }) + if err != nil { + log.Fatal(err) + } + defer cli.Close() + + capi := clientv3.NewCluster(cli) + + resp, err := capi.MemberLeader(context.Background()) + if err != nil { + log.Fatal(err) + } + fmt.Println("leader:", resp.Name) + // leader: infra1 +} + +func ExampleCluster_memberAdd() { + cli, err := clientv3.New(clientv3.Config{ + Endpoints: endpoints[:2], + DialTimeout: dialTimeout, + }) + if err != nil { + log.Fatal(err) + } + defer cli.Close() + + capi := clientv3.NewCluster(cli) + + peerURLs := endpoints[2:] + mresp, err := capi.MemberAdd(context.Background(), peerURLs) + if err != nil { + log.Fatal(err) + } + fmt.Println("added member.PeerURLs:", mresp.Member.PeerURLs) + // added member.PeerURLs: [http://localhost:32380] +} + +func ExampleCluster_memberRemove() { + cli, err := clientv3.New(clientv3.Config{ + Endpoints: endpoints[1:], + DialTimeout: dialTimeout, + }) + if err != nil { + log.Fatal(err) + } + defer cli.Close() + + capi := clientv3.NewCluster(cli) + + resp, err := capi.MemberList(context.Background()) + if err != nil { + log.Fatal(err) + } + + _, err = capi.MemberRemove(context.Background(), resp.Members[0].ID) + if err != nil { + log.Fatal(err) + } +} + +func ExampleCluster_memberUpdate() { + cli, err := clientv3.New(clientv3.Config{ + Endpoints: endpoints, + DialTimeout: dialTimeout, + }) + if err != nil { + log.Fatal(err) + } + defer cli.Close() + + capi := clientv3.NewCluster(cli) + + resp, err := capi.MemberList(context.Background()) + if err != nil { + log.Fatal(err) + } + + peerURLs := []string{"http://localhost:12378"} + _, err = capi.MemberUpdate(context.Background(), resp.Members[0].ID, peerURLs) + if err != nil { + log.Fatal(err) + } +} diff --git a/clientv3/example_kv_test.go b/clientv3/example_kv_test.go index 425b4e6c4..3917aa5c0 100644 --- a/clientv3/example_kv_test.go +++ b/clientv3/example_kv_test.go @@ -17,19 +17,14 @@ package clientv3_test import ( "fmt" "log" - "time" "github.com/coreos/etcd/Godeps/_workspace/src/golang.org/x/net/context" "github.com/coreos/etcd/clientv3" ) func ExampleKV_put() { - var ( - dialTimeout = 5 * time.Second - requestTimeout = 1 * time.Second - ) cli, err := clientv3.New(clientv3.Config{ - Endpoints: []string{"localhost:12378", "localhost:22378", "localhost:32378"}, + Endpoints: endpoints, DialTimeout: dialTimeout, }) if err != nil { @@ -45,17 +40,77 @@ func ExampleKV_put() { if err != nil { log.Fatal(err) } - fmt.Println("OK") - fmt.Println(resp.Header) + fmt.Println("current revision:", resp.Header.Revision) // revision start at 1 + // current revision: 2 } func ExampleKV_get() { - var ( - dialTimeout = 5 * time.Second - requestTimeout = 1 * time.Second - ) cli, err := clientv3.New(clientv3.Config{ - Endpoints: []string{"localhost:12378", "localhost:22378", "localhost:32378"}, + Endpoints: endpoints, + DialTimeout: dialTimeout, + }) + if err != nil { + log.Fatal(err) + } + defer cli.Close() + + kvc := clientv3.NewKV(cli) + + _, err = kvc.Put(context.TODO(), "foo", "bar") + if err != nil { + log.Fatal(err) + } + + ctx, cancel := context.WithTimeout(context.Background(), requestTimeout) + resp, err := kvc.Get(ctx, "foo") + cancel() + if err != nil { + log.Fatal(err) + } + for _, ev := range resp.Kvs { + fmt.Printf("%s : %s\n", ev.Key, ev.Value) + } + // foo : bar +} + +func ExampleKV_getSortedPrefix() { + cli, err := clientv3.New(clientv3.Config{ + Endpoints: endpoints, + DialTimeout: dialTimeout, + }) + if err != nil { + log.Fatal(err) + } + defer cli.Close() + + kvc := clientv3.NewKV(cli) + + for i := range make([]int, 3) { + ctx, cancel := context.WithTimeout(context.Background(), requestTimeout) + _, err = kvc.Put(ctx, fmt.Sprintf("key_%d", i), "value") + cancel() + if err != nil { + log.Fatal(err) + } + } + + ctx, cancel := context.WithTimeout(context.Background(), requestTimeout) + resp, err := kvc.Get(ctx, "key", clientv3.WithPrefix(), clientv3.WithSort(clientv3.SortByKey, clientv3.SortDescend)) + cancel() + if err != nil { + log.Fatal(err) + } + for _, ev := range resp.Kvs { + fmt.Printf("%s : %s\n", ev.Key, ev.Value) + } + // key_2 : value + // key_1 : value + // key_0 : value +} + +func ExampleKV_delete() { + cli, err := clientv3.New(clientv3.Config{ + Endpoints: endpoints, DialTimeout: dialTimeout, }) if err != nil { @@ -66,13 +121,73 @@ func ExampleKV_get() { kvc := clientv3.NewKV(cli) ctx, cancel := context.WithTimeout(context.Background(), requestTimeout) - resp, err := kvc.Get(ctx, "sample_key") + resp, err := kvc.Delete(ctx, "key", clientv3.WithPrefix()) cancel() if err != nil { log.Fatal(err) } - fmt.Println("OK") - for _, ev := range resp.Kvs { + fmt.Println("Deleted", resp.Deleted, "keys") + // Deleted n keys +} + +func ExampleKV_compact() { + cli, err := clientv3.New(clientv3.Config{ + Endpoints: endpoints, + DialTimeout: dialTimeout, + }) + if err != nil { + log.Fatal(err) + } + defer cli.Close() + + kvc := clientv3.NewKV(cli) + + ctx, cancel := context.WithTimeout(context.Background(), requestTimeout) + resp, err := kvc.Get(ctx, "foo") + cancel() + if err != nil { + log.Fatal(err) + } + compRev := resp.Header.Revision // specify compact revision of your choice + + ctx, cancel = context.WithTimeout(context.Background(), requestTimeout) + err = kvc.Compact(ctx, compRev) + cancel() + if err != nil { + log.Fatal(err) + } +} + +func ExampleKV_txn() { + cli, err := clientv3.New(clientv3.Config{ + Endpoints: endpoints, + DialTimeout: dialTimeout, + }) + if err != nil { + log.Fatal(err) + } + defer cli.Close() + + kvc := clientv3.NewKV(cli) + + // TODO: 'if' not working. Add expected output once fixed. + ctx, cancel := context.WithTimeout(context.Background(), requestTimeout) + _, err = kvc.Txn(ctx). + If(clientv3.Compare(clientv3.Value("1"), ">", "1")). + Then(clientv3.OpPut("1", "100")). + Else(clientv3.OpPut("1", "-1")). + Commit() + cancel() + if err == nil { + log.Fatal(err) + } + + gresp, err := kvc.Get(ctx, "1") + cancel() + if err != nil { + log.Fatal(err) + } + for _, ev := range gresp.Kvs { fmt.Printf("%s : %s\n", ev.Key, ev.Value) } } diff --git a/clientv3/example_lease_test.go b/clientv3/example_lease_test.go new file mode 100644 index 000000000..6a4ec1e4a --- /dev/null +++ b/clientv3/example_lease_test.go @@ -0,0 +1,151 @@ +// 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 clientv3_test + +import ( + "fmt" + "log" + + "github.com/coreos/etcd/Godeps/_workspace/src/golang.org/x/net/context" + "github.com/coreos/etcd/clientv3" + "github.com/coreos/etcd/lease" +) + +func ExampleLease_create() { + cli, err := clientv3.New(clientv3.Config{ + Endpoints: endpoints, + DialTimeout: dialTimeout, + }) + if err != nil { + log.Fatal(err) + } + defer cli.Close() + + kvc := clientv3.NewKV(cli) + lapi := clientv3.NewLease(cli) + defer lapi.Close() + + // minimum lease TTL is 5-second + resp, err := lapi.Create(context.TODO(), 5) + if err != nil { + log.Fatal(err) + } + + // after 5 seconds, the key 'foo' will be removed + _, err = kvc.Put(context.TODO(), "foo", "bar", clientv3.WithLease(lease.LeaseID(resp.ID))) + if err != nil { + log.Fatal(err) + } +} + +func ExampleLease_revoke() { + cli, err := clientv3.New(clientv3.Config{ + Endpoints: endpoints, + DialTimeout: dialTimeout, + }) + if err != nil { + log.Fatal(err) + } + defer cli.Close() + + kvc := clientv3.NewKV(cli) + lapi := clientv3.NewLease(cli) + defer lapi.Close() + + resp, err := lapi.Create(context.TODO(), 5) + if err != nil { + log.Fatal(err) + } + + _, err = kvc.Put(context.TODO(), "foo", "bar", clientv3.WithLease(lease.LeaseID(resp.ID))) + if err != nil { + log.Fatal(err) + } + + // revoking lease expires the key attached to its lease ID + _, err = lapi.Revoke(context.TODO(), lease.LeaseID(resp.ID)) + if err != nil { + log.Fatal(err) + } + + gresp, err := kvc.Get(context.TODO(), "foo") + if err != nil { + log.Fatal(err) + } + fmt.Println("number of keys:", len(gresp.Kvs)) + // number of keys: 0 +} + +func ExampleLease_keepAlive() { + cli, err := clientv3.New(clientv3.Config{ + Endpoints: endpoints, + DialTimeout: dialTimeout, + }) + if err != nil { + log.Fatal(err) + } + defer cli.Close() + + kvc := clientv3.NewKV(cli) + lapi := clientv3.NewLease(cli) + defer lapi.Close() + + resp, err := lapi.Create(context.TODO(), 5) + if err != nil { + log.Fatal(err) + } + + _, err = kvc.Put(context.TODO(), "foo", "bar", clientv3.WithLease(lease.LeaseID(resp.ID))) + if err != nil { + log.Fatal(err) + } + + // the key 'foo' will be kept forever + _, err = lapi.KeepAlive(context.TODO(), lease.LeaseID(resp.ID)) + if err != nil { + log.Fatal(err) + } +} + +func ExampleLease_keepAliveOnce() { + cli, err := clientv3.New(clientv3.Config{ + Endpoints: endpoints, + DialTimeout: dialTimeout, + }) + if err != nil { + log.Fatal(err) + } + defer cli.Close() + + kvc := clientv3.NewKV(cli) + lapi := clientv3.NewLease(cli) + defer lapi.Close() + + resp, err := lapi.Create(context.TODO(), 5) + if err != nil { + log.Fatal(err) + } + + _, err = kvc.Put(context.TODO(), "foo", "bar", clientv3.WithLease(lease.LeaseID(resp.ID))) + if err != nil { + log.Fatal(err) + } + + // to renew the lease only once + _, err = lapi.KeepAliveOnce(context.TODO(), lease.LeaseID(resp.ID)) + if err != nil { + log.Fatal(err) + } +} diff --git a/clientv3/example_test.go b/clientv3/example_test.go new file mode 100644 index 000000000..e7eb3b6b7 --- /dev/null +++ b/clientv3/example_test.go @@ -0,0 +1,47 @@ +// 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 clientv3_test + +import ( + "log" + "time" + + "github.com/coreos/etcd/Godeps/_workspace/src/golang.org/x/net/context" + "github.com/coreos/etcd/clientv3" +) + +var ( + dialTimeout = 5 * time.Second + requestTimeout = 1 * time.Second + endpoints = []string{"localhost:12378", "localhost:22378", "http://localhost:32380"} +) + +func Example() { + cli, err := clientv3.New(clientv3.Config{ + Endpoints: endpoints, + DialTimeout: dialTimeout, + }) + if err != nil { + log.Fatal(err) + } + defer cli.Close() // make sure to close the client + + kvc := clientv3.NewKV(cli) + + _, err = kvc.Put(context.TODO(), "foo", "bar") + if err != nil { + log.Fatal(err) + } +} diff --git a/clientv3/example_watch_test.go b/clientv3/example_watch_test.go new file mode 100644 index 000000000..7c7ba1579 --- /dev/null +++ b/clientv3/example_watch_test.go @@ -0,0 +1,67 @@ +// 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 clientv3_test + +import ( + "fmt" + "log" + + "github.com/coreos/etcd/Godeps/_workspace/src/golang.org/x/net/context" + "github.com/coreos/etcd/clientv3" +) + +func ExampleWatcher_watch() { + cli, err := clientv3.New(clientv3.Config{ + Endpoints: endpoints, + DialTimeout: dialTimeout, + }) + if err != nil { + log.Fatal(err) + } + defer cli.Close() + + wc := clientv3.NewWatcher(cli) + defer wc.Close() + + rch := wc.Watch(context.Background(), "foo", 0) + for wresp := range rch { + for _, ev := range wresp.Events { + fmt.Printf("%s %q : %q\n", ev.Type, ev.Kv.Key, ev.Kv.Value) + } + } + // PUT "foo" : "bar" +} + +func ExampleWatcher_watchPrefix() { + cli, err := clientv3.New(clientv3.Config{ + Endpoints: endpoints, + DialTimeout: dialTimeout, + }) + if err != nil { + log.Fatal(err) + } + defer cli.Close() + + wc := clientv3.NewWatcher(cli) + defer wc.Close() + + rch := wc.WatchPrefix(context.Background(), "foo", 0) + for wresp := range rch { + for _, ev := range wresp.Events { + fmt.Printf("%s %q : %q\n", ev.Type, ev.Kv.Key, ev.Kv.Value) + } + } + // PUT "foo1" : "bar" +}