mirror of
https://github.com/etcd-io/etcd.git
synced 2024-09-27 06:25:44 +00:00
tests/integration: Move of naming/snapshot clientv3 tests (and yaml reference)
path change in clientv3/yaml/config_test
git mv clientv3/naming/grpc_test.go tests/integration/clientv3/grpc_test.go
git mv clientv3/snapshot/*_test.go tests/integration/snapshot
git mv clientv3/{main_test.go,example_*.go} tests/integration/clientv3/examples/
This commit is contained in:
113
tests/integration/clientv3/examples/example_auth_test.go
Normal file
113
tests/integration/clientv3/examples/example_auth_test.go
Normal file
@@ -0,0 +1,113 @@
|
||||
// Copyright 2016 The etcd Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package clientv3_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"go.etcd.io/etcd/v3/clientv3"
|
||||
)
|
||||
|
||||
func ExampleAuth() {
|
||||
cli, err := clientv3.New(clientv3.Config{
|
||||
Endpoints: endpoints,
|
||||
DialTimeout: dialTimeout,
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer cli.Close()
|
||||
|
||||
if _, err = cli.RoleAdd(context.TODO(), "root"); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if _, err = cli.UserAdd(context.TODO(), "root", "123"); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if _, err = cli.UserGrantRole(context.TODO(), "root", "root"); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if _, err = cli.RoleAdd(context.TODO(), "r"); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if _, err = cli.RoleGrantPermission(
|
||||
context.TODO(),
|
||||
"r", // role name
|
||||
"foo", // key
|
||||
"zoo", // range end
|
||||
clientv3.PermissionType(clientv3.PermReadWrite),
|
||||
); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if _, err = cli.UserAdd(context.TODO(), "u", "123"); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if _, err = cli.UserGrantRole(context.TODO(), "u", "r"); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if _, err = cli.AuthEnable(context.TODO()); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
cliAuth, err := clientv3.New(clientv3.Config{
|
||||
Endpoints: endpoints,
|
||||
DialTimeout: dialTimeout,
|
||||
Username: "u",
|
||||
Password: "123",
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer cliAuth.Close()
|
||||
|
||||
if _, err = cliAuth.Put(context.TODO(), "foo1", "bar"); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = cliAuth.Txn(context.TODO()).
|
||||
If(clientv3.Compare(clientv3.Value("zoo1"), ">", "abc")).
|
||||
Then(clientv3.OpPut("zoo1", "XYZ")).
|
||||
Else(clientv3.OpPut("zoo1", "ABC")).
|
||||
Commit()
|
||||
fmt.Println(err)
|
||||
|
||||
// now check the permission with the root account
|
||||
rootCli, err := clientv3.New(clientv3.Config{
|
||||
Endpoints: endpoints,
|
||||
DialTimeout: dialTimeout,
|
||||
Username: "root",
|
||||
Password: "123",
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer rootCli.Close()
|
||||
|
||||
resp, err := rootCli.RoleGet(context.TODO(), "r")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Printf("user u permission: key %q, range end %q\n", resp.Perm[0].Key, resp.Perm[0].RangeEnd)
|
||||
|
||||
if _, err = rootCli.AuthDisable(context.TODO()); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
// Output: etcdserver: permission denied
|
||||
// user u permission: key "foo", range end "zoo"
|
||||
}
|
||||
124
tests/integration/clientv3/examples/example_cluster_test.go
Normal file
124
tests/integration/clientv3/examples/example_cluster_test.go
Normal file
@@ -0,0 +1,124 @@
|
||||
// Copyright 2016 The etcd Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package clientv3_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"go.etcd.io/etcd/v3/clientv3"
|
||||
)
|
||||
|
||||
func ExampleCluster_memberList() {
|
||||
cli, err := clientv3.New(clientv3.Config{
|
||||
Endpoints: endpoints,
|
||||
DialTimeout: dialTimeout,
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer cli.Close()
|
||||
|
||||
resp, err := cli.MemberList(context.Background())
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Println("members:", len(resp.Members))
|
||||
// Output: members: 3
|
||||
}
|
||||
|
||||
func ExampleCluster_memberAdd() {
|
||||
cli, err := clientv3.New(clientv3.Config{
|
||||
Endpoints: endpoints[:2],
|
||||
DialTimeout: dialTimeout,
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer cli.Close()
|
||||
|
||||
peerURLs := endpoints[2:]
|
||||
mresp, err := cli.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_memberAddAsLearner() {
|
||||
cli, err := clientv3.New(clientv3.Config{
|
||||
Endpoints: endpoints[:2],
|
||||
DialTimeout: dialTimeout,
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer cli.Close()
|
||||
|
||||
peerURLs := endpoints[2:]
|
||||
mresp, err := cli.MemberAddAsLearner(context.Background(), peerURLs)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Println("added member.PeerURLs:", mresp.Member.PeerURLs)
|
||||
fmt.Println("added member.IsLearner:", mresp.Member.IsLearner)
|
||||
// added member.PeerURLs: [http://localhost:32380]
|
||||
// added member.IsLearner: true
|
||||
}
|
||||
|
||||
func ExampleCluster_memberRemove() {
|
||||
cli, err := clientv3.New(clientv3.Config{
|
||||
Endpoints: endpoints[1:],
|
||||
DialTimeout: dialTimeout,
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer cli.Close()
|
||||
|
||||
resp, err := cli.MemberList(context.Background())
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = cli.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()
|
||||
|
||||
resp, err := cli.MemberList(context.Background())
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
peerURLs := []string{"http://localhost:12380"}
|
||||
_, err = cli.MemberUpdate(context.Background(), resp.Members[0].ID, peerURLs)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
281
tests/integration/clientv3/examples/example_kv_test.go
Normal file
281
tests/integration/clientv3/examples/example_kv_test.go
Normal file
@@ -0,0 +1,281 @@
|
||||
// Copyright 2016 The etcd Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package clientv3_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"go.etcd.io/etcd/api/v3/v3rpc/rpctypes"
|
||||
"go.etcd.io/etcd/v3/clientv3"
|
||||
)
|
||||
|
||||
func ExampleKV_put() {
|
||||
cli, err := clientv3.New(clientv3.Config{
|
||||
Endpoints: endpoints,
|
||||
DialTimeout: dialTimeout,
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer cli.Close()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), requestTimeout)
|
||||
_, err = cli.Put(ctx, "sample_key", "sample_value")
|
||||
cancel()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleKV_putErrorHandling() {
|
||||
cli, err := clientv3.New(clientv3.Config{
|
||||
Endpoints: endpoints,
|
||||
DialTimeout: dialTimeout,
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer cli.Close()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), requestTimeout)
|
||||
_, err = cli.Put(ctx, "", "sample_value")
|
||||
cancel()
|
||||
if err != nil {
|
||||
switch err {
|
||||
case context.Canceled:
|
||||
fmt.Printf("ctx is canceled by another routine: %v\n", err)
|
||||
case context.DeadlineExceeded:
|
||||
fmt.Printf("ctx is attached with a deadline is exceeded: %v\n", err)
|
||||
case rpctypes.ErrEmptyKey:
|
||||
fmt.Printf("client-side error: %v\n", err)
|
||||
default:
|
||||
fmt.Printf("bad cluster endpoints, which are not etcd servers: %v\n", err)
|
||||
}
|
||||
}
|
||||
// Output: client-side error: etcdserver: key is not provided
|
||||
}
|
||||
|
||||
func ExampleKV_get() {
|
||||
cli, err := clientv3.New(clientv3.Config{
|
||||
Endpoints: endpoints,
|
||||
DialTimeout: dialTimeout,
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer cli.Close()
|
||||
|
||||
_, err = cli.Put(context.TODO(), "foo", "bar")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), requestTimeout)
|
||||
resp, err := cli.Get(ctx, "foo")
|
||||
cancel()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
for _, ev := range resp.Kvs {
|
||||
fmt.Printf("%s : %s\n", ev.Key, ev.Value)
|
||||
}
|
||||
// Output: foo : bar
|
||||
}
|
||||
|
||||
func ExampleKV_getWithRev() {
|
||||
cli, err := clientv3.New(clientv3.Config{
|
||||
Endpoints: endpoints,
|
||||
DialTimeout: dialTimeout,
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer cli.Close()
|
||||
|
||||
presp, err := cli.Put(context.TODO(), "foo", "bar1")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
_, err = cli.Put(context.TODO(), "foo", "bar2")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), requestTimeout)
|
||||
resp, err := cli.Get(ctx, "foo", clientv3.WithRev(presp.Header.Revision))
|
||||
cancel()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
for _, ev := range resp.Kvs {
|
||||
fmt.Printf("%s : %s\n", ev.Key, ev.Value)
|
||||
}
|
||||
// Output: foo : bar1
|
||||
}
|
||||
|
||||
func ExampleKV_getSortedPrefix() {
|
||||
cli, err := clientv3.New(clientv3.Config{
|
||||
Endpoints: endpoints,
|
||||
DialTimeout: dialTimeout,
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer cli.Close()
|
||||
|
||||
for i := range make([]int, 3) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), requestTimeout)
|
||||
_, err = cli.Put(ctx, fmt.Sprintf("key_%d", i), "value")
|
||||
cancel()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), requestTimeout)
|
||||
resp, err := cli.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)
|
||||
}
|
||||
// Output:
|
||||
// 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 {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer cli.Close()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), requestTimeout)
|
||||
defer cancel()
|
||||
|
||||
// count keys about to be deleted
|
||||
gresp, err := cli.Get(ctx, "key", clientv3.WithPrefix())
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// delete the keys
|
||||
dresp, err := cli.Delete(ctx, "key", clientv3.WithPrefix())
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
fmt.Println("Deleted all keys:", int64(len(gresp.Kvs)) == dresp.Deleted)
|
||||
// Output:
|
||||
// Deleted all keys: true
|
||||
}
|
||||
|
||||
func ExampleKV_compact() {
|
||||
cli, err := clientv3.New(clientv3.Config{
|
||||
Endpoints: endpoints,
|
||||
DialTimeout: dialTimeout,
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer cli.Close()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), requestTimeout)
|
||||
resp, err := cli.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 = cli.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)
|
||||
|
||||
_, err = kvc.Put(context.TODO(), "key", "xyz")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), requestTimeout)
|
||||
_, err = kvc.Txn(ctx).
|
||||
// txn value comparisons are lexical
|
||||
If(clientv3.Compare(clientv3.Value("key"), ">", "abc")).
|
||||
// the "Then" runs, since "xyz" > "abc"
|
||||
Then(clientv3.OpPut("key", "XYZ")).
|
||||
// the "Else" does not run
|
||||
Else(clientv3.OpPut("key", "ABC")).
|
||||
Commit()
|
||||
cancel()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
gresp, err := kvc.Get(context.TODO(), "key")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
for _, ev := range gresp.Kvs {
|
||||
fmt.Printf("%s : %s\n", ev.Key, ev.Value)
|
||||
}
|
||||
// Output: key : XYZ
|
||||
}
|
||||
|
||||
func ExampleKV_do() {
|
||||
cli, err := clientv3.New(clientv3.Config{
|
||||
Endpoints: endpoints,
|
||||
DialTimeout: dialTimeout,
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer cli.Close()
|
||||
|
||||
ops := []clientv3.Op{
|
||||
clientv3.OpPut("put-key", "123"),
|
||||
clientv3.OpGet("put-key"),
|
||||
clientv3.OpPut("put-key", "456")}
|
||||
|
||||
for _, op := range ops {
|
||||
if _, err := cli.Do(context.TODO(), op); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
141
tests/integration/clientv3/examples/example_lease_test.go
Normal file
141
tests/integration/clientv3/examples/example_lease_test.go
Normal file
@@ -0,0 +1,141 @@
|
||||
// Copyright 2016 The etcd Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package clientv3_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"go.etcd.io/etcd/v3/clientv3"
|
||||
)
|
||||
|
||||
func ExampleLease_grant() {
|
||||
cli, err := clientv3.New(clientv3.Config{
|
||||
Endpoints: endpoints,
|
||||
DialTimeout: dialTimeout,
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer cli.Close()
|
||||
|
||||
// minimum lease TTL is 5-second
|
||||
resp, err := cli.Grant(context.TODO(), 5)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// after 5 seconds, the key 'foo' will be removed
|
||||
_, err = cli.Put(context.TODO(), "foo", "bar", clientv3.WithLease(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()
|
||||
|
||||
resp, err := cli.Grant(context.TODO(), 5)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = cli.Put(context.TODO(), "foo", "bar", clientv3.WithLease(resp.ID))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// revoking lease expires the key attached to its lease ID
|
||||
_, err = cli.Revoke(context.TODO(), resp.ID)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
gresp, err := cli.Get(context.TODO(), "foo")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Println("number of keys:", len(gresp.Kvs))
|
||||
// Output: 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()
|
||||
|
||||
resp, err := cli.Grant(context.TODO(), 5)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = cli.Put(context.TODO(), "foo", "bar", clientv3.WithLease(resp.ID))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// the key 'foo' will be kept forever
|
||||
ch, kaerr := cli.KeepAlive(context.TODO(), resp.ID)
|
||||
if kaerr != nil {
|
||||
log.Fatal(kaerr)
|
||||
}
|
||||
|
||||
ka := <-ch
|
||||
fmt.Println("ttl:", ka.TTL)
|
||||
// Output: ttl: 5
|
||||
}
|
||||
|
||||
func ExampleLease_keepAliveOnce() {
|
||||
cli, err := clientv3.New(clientv3.Config{
|
||||
Endpoints: endpoints,
|
||||
DialTimeout: dialTimeout,
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer cli.Close()
|
||||
|
||||
resp, err := cli.Grant(context.TODO(), 5)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = cli.Put(context.TODO(), "foo", "bar", clientv3.WithLease(resp.ID))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// to renew the lease only once
|
||||
ka, kaerr := cli.KeepAliveOnce(context.TODO(), resp.ID)
|
||||
if kaerr != nil {
|
||||
log.Fatal(kaerr)
|
||||
}
|
||||
|
||||
fmt.Println("ttl:", ka.TTL)
|
||||
// Output: ttl: 5
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
// Copyright 2016 The etcd Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package clientv3_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"go.etcd.io/etcd/v3/clientv3"
|
||||
)
|
||||
|
||||
func ExampleMaintenance_status() {
|
||||
for _, ep := range endpoints {
|
||||
cli, err := clientv3.New(clientv3.Config{
|
||||
Endpoints: []string{ep},
|
||||
DialTimeout: dialTimeout,
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer cli.Close()
|
||||
|
||||
resp, err := cli.Status(context.Background(), ep)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Printf("endpoint: %s / Leader: %v\n", ep, resp.Header.MemberId == resp.Leader)
|
||||
}
|
||||
// endpoint: localhost:2379 / Leader: false
|
||||
// endpoint: localhost:22379 / Leader: false
|
||||
// endpoint: localhost:32379 / Leader: true
|
||||
}
|
||||
|
||||
func ExampleMaintenance_defragment() {
|
||||
for _, ep := range endpoints {
|
||||
cli, err := clientv3.New(clientv3.Config{
|
||||
Endpoints: []string{ep},
|
||||
DialTimeout: dialTimeout,
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer cli.Close()
|
||||
|
||||
if _, err = cli.Defragment(context.TODO(), ep); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
85
tests/integration/clientv3/examples/example_metrics_test.go
Normal file
85
tests/integration/clientv3/examples/example_metrics_test.go
Normal file
@@ -0,0 +1,85 @@
|
||||
// Copyright 2016 The etcd Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package clientv3_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"go.etcd.io/etcd/v3/clientv3"
|
||||
|
||||
grpcprom "github.com/grpc-ecosystem/go-grpc-prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
func ExampleClient_metrics() {
|
||||
cli, err := clientv3.New(clientv3.Config{
|
||||
Endpoints: endpoints,
|
||||
DialOptions: []grpc.DialOption{
|
||||
grpc.WithUnaryInterceptor(grpcprom.UnaryClientInterceptor),
|
||||
grpc.WithStreamInterceptor(grpcprom.StreamClientInterceptor),
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer cli.Close()
|
||||
|
||||
// get a key so it shows up in the metrics as a range RPC
|
||||
cli.Get(context.TODO(), "test_key")
|
||||
|
||||
// listen for all Prometheus metrics
|
||||
ln, err := net.Listen("tcp", ":0")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
donec := make(chan struct{})
|
||||
go func() {
|
||||
defer close(donec)
|
||||
http.Serve(ln, promhttp.Handler())
|
||||
}()
|
||||
defer func() {
|
||||
ln.Close()
|
||||
<-donec
|
||||
}()
|
||||
|
||||
// make an http request to fetch all Prometheus metrics
|
||||
url := "http://" + ln.Addr().String() + "/metrics"
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
log.Fatalf("fetch error: %v", err)
|
||||
}
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
resp.Body.Close()
|
||||
if err != nil {
|
||||
log.Fatalf("fetch error: reading %s: %v", url, err)
|
||||
}
|
||||
|
||||
// confirm range request in metrics
|
||||
for _, l := range strings.Split(string(b), "\n") {
|
||||
if strings.Contains(l, `grpc_client_started_total{grpc_method="Range"`) {
|
||||
fmt.Println(l)
|
||||
break
|
||||
}
|
||||
}
|
||||
// Output:
|
||||
// grpc_client_started_total{grpc_method="Range",grpc_service="etcdserverpb.KV",grpc_type="unary"} 1
|
||||
}
|
||||
70
tests/integration/clientv3/examples/example_test.go
Normal file
70
tests/integration/clientv3/examples/example_test.go
Normal file
@@ -0,0 +1,70 @@
|
||||
// Copyright 2016 The etcd Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package clientv3_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"go.etcd.io/etcd/v3/clientv3"
|
||||
"go.etcd.io/etcd/v3/pkg/transport"
|
||||
"log"
|
||||
)
|
||||
|
||||
func ExampleConfig_insecure() {
|
||||
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
|
||||
|
||||
_, err = cli.Put(context.TODO(), "foo", "bar")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Without the line below the test is not being executed
|
||||
|
||||
// Output:
|
||||
}
|
||||
|
||||
func ExampleConfig_withTLS() {
|
||||
tlsInfo := transport.TLSInfo{
|
||||
CertFile: "/tmp/test-certs/test-name-1.pem",
|
||||
KeyFile: "/tmp/test-certs/test-name-1-key.pem",
|
||||
TrustedCAFile: "/tmp/test-certs/trusted-ca.pem",
|
||||
}
|
||||
tlsConfig, err := tlsInfo.ClientConfig()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
cli, err := clientv3.New(clientv3.Config{
|
||||
Endpoints: endpoints,
|
||||
DialTimeout: dialTimeout,
|
||||
TLS: tlsConfig,
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer cli.Close() // make sure to close the client
|
||||
|
||||
_, err = cli.Put(context.TODO(), "foo", "bar")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
// Without the line below the test is not being executed
|
||||
// Output:
|
||||
}
|
||||
100
tests/integration/clientv3/examples/example_watch_test.go
Normal file
100
tests/integration/clientv3/examples/example_watch_test.go
Normal file
@@ -0,0 +1,100 @@
|
||||
// Copyright 2016 The etcd Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package clientv3_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"go.etcd.io/etcd/v3/clientv3"
|
||||
)
|
||||
|
||||
func ExampleWatcher_watch() {
|
||||
cli, err := clientv3.New(clientv3.Config{
|
||||
Endpoints: endpoints,
|
||||
DialTimeout: dialTimeout,
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer cli.Close()
|
||||
|
||||
rch := cli.Watch(context.Background(), "foo")
|
||||
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_watchWithPrefix() {
|
||||
cli, err := clientv3.New(clientv3.Config{
|
||||
Endpoints: endpoints,
|
||||
DialTimeout: dialTimeout,
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer cli.Close()
|
||||
|
||||
rch := cli.Watch(context.Background(), "foo", clientv3.WithPrefix())
|
||||
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"
|
||||
}
|
||||
|
||||
func ExampleWatcher_watchWithRange() {
|
||||
cli, err := clientv3.New(clientv3.Config{
|
||||
Endpoints: endpoints,
|
||||
DialTimeout: dialTimeout,
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer cli.Close()
|
||||
|
||||
// watches within ['foo1', 'foo4'), in lexicographical order
|
||||
rch := cli.Watch(context.Background(), "foo1", clientv3.WithRange("foo4"))
|
||||
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"
|
||||
// PUT "foo2" : "bar"
|
||||
// PUT "foo3" : "bar"
|
||||
}
|
||||
|
||||
func ExampleWatcher_watchWithProgressNotify() {
|
||||
cli, err := clientv3.New(clientv3.Config{
|
||||
Endpoints: endpoints,
|
||||
DialTimeout: dialTimeout,
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
rch := cli.Watch(context.Background(), "foo", clientv3.WithProgressNotify())
|
||||
wresp := <-rch
|
||||
fmt.Printf("wresp.Header.Revision: %d\n", wresp.Header.Revision)
|
||||
fmt.Println("wresp.IsProgressNotify:", wresp.IsProgressNotify())
|
||||
// wresp.Header.Revision: 0
|
||||
// wresp.IsProgressNotify: true
|
||||
}
|
||||
78
tests/integration/clientv3/examples/main_test.go
Normal file
78
tests/integration/clientv3/examples/main_test.go
Normal file
@@ -0,0 +1,78 @@
|
||||
// Copyright 2016 The etcd Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package clientv3_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go.etcd.io/etcd/tests/v3/integration"
|
||||
"go.etcd.io/etcd/v3/clientv3"
|
||||
"go.etcd.io/etcd/v3/pkg/testutil"
|
||||
"google.golang.org/grpc/grpclog"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
dialTimeout = 5 * time.Second
|
||||
requestTimeout = 10 * time.Second
|
||||
endpoints = []string{"localhost:2379", "localhost:22379", "localhost:32379"}
|
||||
)
|
||||
|
||||
// TestMain sets up an etcd cluster if running the examples.
|
||||
func TestMain(m *testing.M) {
|
||||
useCluster, hasRunArg := false, false // default to running only Test*
|
||||
for _, arg := range os.Args {
|
||||
if strings.HasPrefix(arg, "-test.run=") {
|
||||
exp := strings.Split(arg, "=")[1]
|
||||
useCluster = strings.Contains(exp, "Example")
|
||||
hasRunArg = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !hasRunArg {
|
||||
// force only running Test* if no args given to avoid leak false
|
||||
// positives from having a long-running cluster for the examples.
|
||||
os.Args = append(os.Args, "-test.run=Test")
|
||||
}
|
||||
|
||||
var v int
|
||||
if useCluster {
|
||||
// Redirecting outputs to Stderr, such that they not interleave with examples outputs.
|
||||
// Setting it once and before running any of the test such that it not data-races
|
||||
// between HTTP servers running in different tests.
|
||||
clientv3.SetLogger(grpclog.NewLoggerV2(os.Stderr, os.Stderr, os.Stderr))
|
||||
|
||||
cfg := integration.ClusterConfig{Size: 3}
|
||||
clus := integration.NewClusterV3(nil, &cfg)
|
||||
endpoints = make([]string, 3)
|
||||
for i := range endpoints {
|
||||
endpoints[i] = clus.Client(i).Endpoints()[0]
|
||||
}
|
||||
v = m.Run()
|
||||
clus.Terminate(nil)
|
||||
if err := testutil.CheckAfterTest(time.Second); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "%v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
} else {
|
||||
v = m.Run()
|
||||
}
|
||||
if v == 0 && testutil.CheckLeakedGoroutine() {
|
||||
os.Exit(1)
|
||||
}
|
||||
os.Exit(v)
|
||||
}
|
||||
139
tests/integration/clientv3/grpc_test.go
Normal file
139
tests/integration/clientv3/grpc_test.go
Normal file
@@ -0,0 +1,139 @@
|
||||
// Copyright 2016 The etcd Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package naming_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"go.etcd.io/etcd/tests/v3/integration"
|
||||
etcd "go.etcd.io/etcd/v3/clientv3"
|
||||
namingv3 "go.etcd.io/etcd/v3/clientv3/naming"
|
||||
"go.etcd.io/etcd/v3/pkg/testutil"
|
||||
|
||||
"google.golang.org/grpc/naming"
|
||||
)
|
||||
|
||||
func TestGRPCResolver(t *testing.T) {
|
||||
defer testutil.AfterTest(t)
|
||||
|
||||
clus := integration.NewClusterV3(t, &integration.ClusterConfig{Size: 1})
|
||||
defer clus.Terminate(t)
|
||||
|
||||
r := namingv3.GRPCResolver{
|
||||
Client: clus.RandClient(),
|
||||
}
|
||||
|
||||
w, err := r.Resolve("foo")
|
||||
if err != nil {
|
||||
t.Fatal("failed to resolve foo", err)
|
||||
}
|
||||
defer w.Close()
|
||||
|
||||
addOp := naming.Update{Op: naming.Add, Addr: "127.0.0.1", Metadata: "metadata"}
|
||||
err = r.Update(context.TODO(), "foo", addOp)
|
||||
if err != nil {
|
||||
t.Fatal("failed to add foo", err)
|
||||
}
|
||||
|
||||
us, err := w.Next()
|
||||
if err != nil {
|
||||
t.Fatal("failed to get udpate", err)
|
||||
}
|
||||
|
||||
wu := &naming.Update{
|
||||
Op: naming.Add,
|
||||
Addr: "127.0.0.1",
|
||||
Metadata: "metadata",
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(us[0], wu) {
|
||||
t.Fatalf("up = %#v, want %#v", us[0], wu)
|
||||
}
|
||||
|
||||
delOp := naming.Update{Op: naming.Delete, Addr: "127.0.0.1"}
|
||||
err = r.Update(context.TODO(), "foo", delOp)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to udpate %v", err)
|
||||
}
|
||||
|
||||
us, err = w.Next()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to get udpate %v", err)
|
||||
}
|
||||
|
||||
wu = &naming.Update{
|
||||
Op: naming.Delete,
|
||||
Addr: "127.0.0.1",
|
||||
Metadata: "metadata",
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(us[0], wu) {
|
||||
t.Fatalf("up = %#v, want %#v", us[0], wu)
|
||||
}
|
||||
}
|
||||
|
||||
// TestGRPCResolverMulti ensures the resolver will initialize
|
||||
// correctly with multiple hosts and correctly receive multiple
|
||||
// updates in a single revision.
|
||||
func TestGRPCResolverMulti(t *testing.T) {
|
||||
defer testutil.AfterTest(t)
|
||||
|
||||
clus := integration.NewClusterV3(t, &integration.ClusterConfig{Size: 1})
|
||||
defer clus.Terminate(t)
|
||||
c := clus.RandClient()
|
||||
|
||||
v, verr := json.Marshal(naming.Update{Addr: "127.0.0.1", Metadata: "md"})
|
||||
if verr != nil {
|
||||
t.Fatal(verr)
|
||||
}
|
||||
if _, err := c.Put(context.TODO(), "foo/host", string(v)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := c.Put(context.TODO(), "foo/host2", string(v)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
r := namingv3.GRPCResolver{Client: c}
|
||||
|
||||
w, err := r.Resolve("foo")
|
||||
if err != nil {
|
||||
t.Fatal("failed to resolve foo", err)
|
||||
}
|
||||
defer w.Close()
|
||||
|
||||
updates, nerr := w.Next()
|
||||
if nerr != nil {
|
||||
t.Fatal(nerr)
|
||||
}
|
||||
if len(updates) != 2 {
|
||||
t.Fatalf("expected two updates, got %+v", updates)
|
||||
}
|
||||
|
||||
_, err = c.Txn(context.TODO()).Then(etcd.OpDelete("foo/host"), etcd.OpDelete("foo/host2")).Commit()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
updates, nerr = w.Next()
|
||||
if nerr != nil {
|
||||
t.Fatal(nerr)
|
||||
}
|
||||
if len(updates) != 2 || (updates[0].Op != naming.Delete && updates[1].Op != naming.Delete) {
|
||||
t.Fatalf("expected two updates, got %+v", updates)
|
||||
}
|
||||
}
|
||||
153
tests/integration/clientv3/ordering_util_test.go
Normal file
153
tests/integration/clientv3/ordering_util_test.go
Normal file
@@ -0,0 +1,153 @@
|
||||
// Copyright 2017 The etcd Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package ordering
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"go.etcd.io/etcd/tests/v3/integration"
|
||||
"go.etcd.io/etcd/v3/clientv3"
|
||||
"go.etcd.io/etcd/v3/pkg/testutil"
|
||||
)
|
||||
|
||||
func TestEndpointSwitchResolvesViolation(t *testing.T) {
|
||||
defer testutil.AfterTest(t)
|
||||
clus := integration.NewClusterV3(t, &integration.ClusterConfig{Size: 3})
|
||||
defer clus.Terminate(t)
|
||||
eps := []string{
|
||||
clus.Members[0].GRPCAddr(),
|
||||
clus.Members[1].GRPCAddr(),
|
||||
clus.Members[2].GRPCAddr(),
|
||||
}
|
||||
cfg := clientv3.Config{Endpoints: []string{clus.Members[0].GRPCAddr()}}
|
||||
cli, err := clientv3.New(cfg)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
ctx := context.TODO()
|
||||
|
||||
if _, err = clus.Client(0).Put(ctx, "foo", "bar"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// ensure that the second member has current revision for key "foo"
|
||||
if _, err = clus.Client(1).Get(ctx, "foo"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// create partition between third members and the first two members
|
||||
// in order to guarantee that the third member's revision of "foo"
|
||||
// falls behind as updates to "foo" are issued to the first two members.
|
||||
clus.Members[2].InjectPartition(t, clus.Members[:2]...)
|
||||
time.Sleep(1 * time.Second) // give enough time for the operation
|
||||
|
||||
// update to "foo" will not be replicated to the third member due to the partition
|
||||
if _, err = clus.Client(1).Put(ctx, "foo", "buzz"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// reset client endpoints to all members such that the copy of cli sent to
|
||||
// NewOrderViolationSwitchEndpointClosure will be able to
|
||||
// access the full list of endpoints.
|
||||
cli.SetEndpoints(eps...)
|
||||
OrderingKv := NewKV(cli.KV, NewOrderViolationSwitchEndpointClosure(*cli))
|
||||
// set prevRev to the second member's revision of "foo" such that
|
||||
// the revision is higher than the third member's revision of "foo"
|
||||
_, err = OrderingKv.Get(ctx, "foo")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
cli.SetEndpoints(clus.Members[2].GRPCAddr())
|
||||
time.Sleep(1 * time.Second) // give enough time for operation
|
||||
_, err = OrderingKv.Get(ctx, "foo", clientv3.WithSerializable())
|
||||
if err != nil {
|
||||
t.Fatalf("failed to resolve order violation %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnresolvableOrderViolation(t *testing.T) {
|
||||
defer testutil.AfterTest(t)
|
||||
clus := integration.NewClusterV3(t, &integration.ClusterConfig{Size: 5, SkipCreatingClient: true})
|
||||
defer clus.Terminate(t)
|
||||
cfg := clientv3.Config{
|
||||
Endpoints: []string{
|
||||
clus.Members[0].GRPCAddr(),
|
||||
clus.Members[1].GRPCAddr(),
|
||||
clus.Members[2].GRPCAddr(),
|
||||
clus.Members[3].GRPCAddr(),
|
||||
clus.Members[4].GRPCAddr(),
|
||||
},
|
||||
}
|
||||
cli, err := clientv3.New(cfg)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
eps := cli.Endpoints()
|
||||
ctx := context.TODO()
|
||||
|
||||
cli.SetEndpoints(clus.Members[0].GRPCAddr())
|
||||
time.Sleep(1 * time.Second)
|
||||
_, err = cli.Put(ctx, "foo", "bar")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// stop fourth member in order to force the member to have an outdated revision
|
||||
clus.Members[3].Stop(t)
|
||||
time.Sleep(1 * time.Second) // give enough time for operation
|
||||
// stop fifth member in order to force the member to have an outdated revision
|
||||
clus.Members[4].Stop(t)
|
||||
time.Sleep(1 * time.Second) // give enough time for operation
|
||||
_, err = cli.Put(ctx, "foo", "buzz")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// reset client endpoints to all members such that the copy of cli sent to
|
||||
// NewOrderViolationSwitchEndpointClosure will be able to
|
||||
// access the full list of endpoints.
|
||||
cli.SetEndpoints(eps...)
|
||||
time.Sleep(1 * time.Second) // give enough time for operation
|
||||
OrderingKv := NewKV(cli.KV, NewOrderViolationSwitchEndpointClosure(*cli))
|
||||
// set prevRev to the first member's revision of "foo" such that
|
||||
// the revision is higher than the fourth and fifth members' revision of "foo"
|
||||
_, err = OrderingKv.Get(ctx, "foo")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
clus.Members[0].Stop(t)
|
||||
clus.Members[1].Stop(t)
|
||||
clus.Members[2].Stop(t)
|
||||
err = clus.Members[3].Restart(t)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = clus.Members[4].Restart(t)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
clus.Members[3].WaitStarted(t)
|
||||
cli.SetEndpoints(clus.Members[3].GRPCAddr())
|
||||
time.Sleep(5 * time.Second) // give enough time for operation
|
||||
|
||||
_, err = OrderingKv.Get(ctx, "foo", clientv3.WithSerializable())
|
||||
if err != ErrNoGreaterRev {
|
||||
t.Fatalf("expected %v, got %v", ErrNoGreaterRev, err)
|
||||
}
|
||||
}
|
||||
127
tests/integration/snapshot/member_test.go
Normal file
127
tests/integration/snapshot/member_test.go
Normal file
@@ -0,0 +1,127 @@
|
||||
// Copyright 2018 The etcd Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package snapshot
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"go.etcd.io/etcd/v3/clientv3"
|
||||
"go.etcd.io/etcd/v3/embed"
|
||||
"go.etcd.io/etcd/v3/etcdserver"
|
||||
"go.etcd.io/etcd/v3/pkg/testutil"
|
||||
)
|
||||
|
||||
// TestSnapshotV3RestoreMultiMemberAdd ensures that multiple members
|
||||
// can boot into the same cluster after being restored from a same
|
||||
// snapshot file, and also be able to add another member to the cluster.
|
||||
func TestSnapshotV3RestoreMultiMemberAdd(t *testing.T) {
|
||||
kvs := []kv{{"foo1", "bar1"}, {"foo2", "bar2"}, {"foo3", "bar3"}}
|
||||
dbPath := createSnapshotFile(t, kvs)
|
||||
|
||||
clusterN := 3
|
||||
cURLs, pURLs, srvs := restoreCluster(t, clusterN, dbPath)
|
||||
defer func() {
|
||||
for i := 0; i < clusterN; i++ {
|
||||
os.RemoveAll(srvs[i].Config().Dir)
|
||||
srvs[i].Close()
|
||||
}
|
||||
}()
|
||||
|
||||
// wait for health interval + leader election
|
||||
time.Sleep(etcdserver.HealthInterval + 2*time.Second)
|
||||
|
||||
cli, err := clientv3.New(clientv3.Config{Endpoints: []string{cURLs[0].String()}})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer cli.Close()
|
||||
|
||||
urls := newEmbedURLs(2)
|
||||
newCURLs, newPURLs := urls[:1], urls[1:]
|
||||
if _, err = cli.MemberAdd(context.Background(), []string{newPURLs[0].String()}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// wait for membership reconfiguration apply
|
||||
time.Sleep(testutil.ApplyTimeout)
|
||||
|
||||
cfg := embed.NewConfig()
|
||||
cfg.Logger = "zap"
|
||||
cfg.LogOutputs = []string{"/dev/null"}
|
||||
cfg.Name = "3"
|
||||
cfg.InitialClusterToken = testClusterTkn
|
||||
cfg.ClusterState = "existing"
|
||||
cfg.LCUrls, cfg.ACUrls = newCURLs, newCURLs
|
||||
cfg.LPUrls, cfg.APUrls = newPURLs, newPURLs
|
||||
cfg.InitialCluster = ""
|
||||
for i := 0; i < clusterN; i++ {
|
||||
cfg.InitialCluster += fmt.Sprintf(",%d=%s", i, pURLs[i].String())
|
||||
}
|
||||
cfg.InitialCluster = cfg.InitialCluster[1:]
|
||||
cfg.InitialCluster += fmt.Sprintf(",%s=%s", cfg.Name, newPURLs[0].String())
|
||||
cfg.Dir = filepath.Join(os.TempDir(), fmt.Sprint(time.Now().Nanosecond()))
|
||||
|
||||
srv, err := embed.StartEtcd(cfg)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer func() {
|
||||
os.RemoveAll(cfg.Dir)
|
||||
srv.Close()
|
||||
}()
|
||||
select {
|
||||
case <-srv.Server.ReadyNotify():
|
||||
case <-time.After(10 * time.Second):
|
||||
t.Fatalf("failed to start the newly added etcd member")
|
||||
}
|
||||
|
||||
cli2, err := clientv3.New(clientv3.Config{Endpoints: []string{newCURLs[0].String()}})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer cli2.Close()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), testutil.RequestTimeout)
|
||||
mresp, err := cli2.MemberList(ctx)
|
||||
cancel()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(mresp.Members) != 4 {
|
||||
t.Fatalf("expected 4 members, got %+v", mresp)
|
||||
}
|
||||
|
||||
// make sure restored cluster has kept all data on recovery
|
||||
var gresp *clientv3.GetResponse
|
||||
ctx, cancel = context.WithTimeout(context.Background(), testutil.RequestTimeout)
|
||||
gresp, err = cli2.Get(ctx, "foo", clientv3.WithPrefix())
|
||||
cancel()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for i := range gresp.Kvs {
|
||||
if string(gresp.Kvs[i].Key) != kvs[i].k {
|
||||
t.Fatalf("#%d: key expected %s, got %s", i, kvs[i].k, string(gresp.Kvs[i].Key))
|
||||
}
|
||||
if string(gresp.Kvs[i].Value) != kvs[i].v {
|
||||
t.Fatalf("#%d: value expected %s, got %s", i, kvs[i].v, string(gresp.Kvs[i].Value))
|
||||
}
|
||||
}
|
||||
}
|
||||
326
tests/integration/snapshot/v3_snapshot_test.go
Normal file
326
tests/integration/snapshot/v3_snapshot_test.go
Normal file
@@ -0,0 +1,326 @@
|
||||
// Copyright 2018 The etcd Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package snapshot
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"go.etcd.io/etcd/v3/clientv3"
|
||||
"go.etcd.io/etcd/v3/embed"
|
||||
"go.etcd.io/etcd/v3/pkg/fileutil"
|
||||
"go.etcd.io/etcd/v3/pkg/testutil"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// TestSnapshotV3RestoreSingle tests single node cluster restoring
|
||||
// from a snapshot file.
|
||||
func TestSnapshotV3RestoreSingle(t *testing.T) {
|
||||
kvs := []kv{{"foo1", "bar1"}, {"foo2", "bar2"}, {"foo3", "bar3"}}
|
||||
dbPath := createSnapshotFile(t, kvs)
|
||||
defer os.RemoveAll(dbPath)
|
||||
|
||||
clusterN := 1
|
||||
urls := newEmbedURLs(clusterN * 2)
|
||||
cURLs, pURLs := urls[:clusterN], urls[clusterN:]
|
||||
|
||||
cfg := embed.NewConfig()
|
||||
cfg.Logger = "zap"
|
||||
cfg.LogOutputs = []string{"/dev/null"}
|
||||
cfg.Name = "s1"
|
||||
cfg.InitialClusterToken = testClusterTkn
|
||||
cfg.ClusterState = "existing"
|
||||
cfg.LCUrls, cfg.ACUrls = cURLs, cURLs
|
||||
cfg.LPUrls, cfg.APUrls = pURLs, pURLs
|
||||
cfg.InitialCluster = fmt.Sprintf("%s=%s", cfg.Name, pURLs[0].String())
|
||||
cfg.Dir = filepath.Join(os.TempDir(), fmt.Sprint(time.Now().Nanosecond()))
|
||||
|
||||
sp := NewV3(zap.NewExample())
|
||||
pss := make([]string, 0, len(pURLs))
|
||||
for _, p := range pURLs {
|
||||
pss = append(pss, p.String())
|
||||
}
|
||||
if err := sp.Restore(RestoreConfig{
|
||||
SnapshotPath: dbPath,
|
||||
Name: cfg.Name,
|
||||
OutputDataDir: cfg.Dir,
|
||||
InitialCluster: cfg.InitialCluster,
|
||||
InitialClusterToken: cfg.InitialClusterToken,
|
||||
PeerURLs: pss,
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
srv, err := embed.StartEtcd(cfg)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer func() {
|
||||
os.RemoveAll(cfg.Dir)
|
||||
srv.Close()
|
||||
}()
|
||||
select {
|
||||
case <-srv.Server.ReadyNotify():
|
||||
case <-time.After(3 * time.Second):
|
||||
t.Fatalf("failed to start restored etcd member")
|
||||
}
|
||||
|
||||
var cli *clientv3.Client
|
||||
cli, err = clientv3.New(clientv3.Config{Endpoints: []string{cfg.ACUrls[0].String()}})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer cli.Close()
|
||||
for i := range kvs {
|
||||
var gresp *clientv3.GetResponse
|
||||
gresp, err = cli.Get(context.Background(), kvs[i].k)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if string(gresp.Kvs[0].Value) != kvs[i].v {
|
||||
t.Fatalf("#%d: value expected %s, got %s", i, kvs[i].v, string(gresp.Kvs[0].Value))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestSnapshotV3RestoreMulti ensures that multiple members
|
||||
// can boot into the same cluster after being restored from a same
|
||||
// snapshot file.
|
||||
func TestSnapshotV3RestoreMulti(t *testing.T) {
|
||||
kvs := []kv{{"foo1", "bar1"}, {"foo2", "bar2"}, {"foo3", "bar3"}}
|
||||
dbPath := createSnapshotFile(t, kvs)
|
||||
defer os.RemoveAll(dbPath)
|
||||
|
||||
clusterN := 3
|
||||
cURLs, _, srvs := restoreCluster(t, clusterN, dbPath)
|
||||
defer func() {
|
||||
for i := 0; i < clusterN; i++ {
|
||||
os.RemoveAll(srvs[i].Config().Dir)
|
||||
srvs[i].Close()
|
||||
}
|
||||
}()
|
||||
|
||||
// wait for leader election
|
||||
time.Sleep(time.Second)
|
||||
|
||||
for i := 0; i < clusterN; i++ {
|
||||
cli, err := clientv3.New(clientv3.Config{Endpoints: []string{cURLs[i].String()}})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer cli.Close()
|
||||
for i := range kvs {
|
||||
var gresp *clientv3.GetResponse
|
||||
gresp, err = cli.Get(context.Background(), kvs[i].k)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if string(gresp.Kvs[0].Value) != kvs[i].v {
|
||||
t.Fatalf("#%d: value expected %s, got %s", i, kvs[i].v, string(gresp.Kvs[0].Value))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestSnapshotFilePermissions ensures that the snapshot is saved with
|
||||
// the correct file permissions.
|
||||
func TestSnapshotFilePermissions(t *testing.T) {
|
||||
expectedFileMode := os.FileMode(fileutil.PrivateFileMode)
|
||||
kvs := []kv{{"foo1", "bar1"}, {"foo2", "bar2"}, {"foo3", "bar3"}}
|
||||
dbPath := createSnapshotFile(t, kvs)
|
||||
defer os.RemoveAll(dbPath)
|
||||
|
||||
dbInfo, err := os.Stat(dbPath)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to get test snapshot file status: %v", err)
|
||||
}
|
||||
actualFileMode := dbInfo.Mode()
|
||||
|
||||
if expectedFileMode != actualFileMode {
|
||||
t.Fatalf("expected test snapshot file mode %s, got %s:", expectedFileMode, actualFileMode)
|
||||
}
|
||||
}
|
||||
|
||||
// TestCorruptedBackupFileCheck tests if we can correctly identify a corrupted backup file.
|
||||
func TestCorruptedBackupFileCheck(t *testing.T) {
|
||||
dbPath := "testdata/corrupted_backup.db"
|
||||
if _, err := os.Stat(dbPath); err != nil {
|
||||
t.Fatalf("test file [%s] does not exist: %v", dbPath, err)
|
||||
}
|
||||
|
||||
sp := NewV3(zap.NewExample())
|
||||
_, err := sp.Status(dbPath)
|
||||
expectedErrKeywords := "snapshot file integrity check failed"
|
||||
/* example error message:
|
||||
snapshot file integrity check failed. 2 errors found.
|
||||
page 3: already freed
|
||||
page 4: unreachable unfreed
|
||||
*/
|
||||
if err == nil {
|
||||
t.Error("expected error due to corrupted snapshot file, got no error")
|
||||
}
|
||||
if !strings.Contains(err.Error(), expectedErrKeywords) {
|
||||
t.Errorf("expected error message to contain the following keywords:\n%s\n"+
|
||||
"actual error message:\n%s",
|
||||
expectedErrKeywords, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
type kv struct {
|
||||
k, v string
|
||||
}
|
||||
|
||||
// creates a snapshot file and returns the file path.
|
||||
func createSnapshotFile(t *testing.T, kvs []kv) string {
|
||||
testutil.SkipTestIfShortMode(t,
|
||||
"Snapshot creation tests are depending on embedded etcServer so are integration-level tests.")
|
||||
clusterN := 1
|
||||
urls := newEmbedURLs(clusterN * 2)
|
||||
cURLs, pURLs := urls[:clusterN], urls[clusterN:]
|
||||
|
||||
cfg := embed.NewConfig()
|
||||
cfg.Logger = "zap"
|
||||
cfg.LogOutputs = []string{"/dev/null"}
|
||||
cfg.Name = "default"
|
||||
cfg.ClusterState = "new"
|
||||
cfg.LCUrls, cfg.ACUrls = cURLs, cURLs
|
||||
cfg.LPUrls, cfg.APUrls = pURLs, pURLs
|
||||
cfg.InitialCluster = fmt.Sprintf("%s=%s", cfg.Name, pURLs[0].String())
|
||||
cfg.Dir = filepath.Join(os.TempDir(), fmt.Sprint(time.Now().Nanosecond()))
|
||||
srv, err := embed.StartEtcd(cfg)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer func() {
|
||||
os.RemoveAll(cfg.Dir)
|
||||
srv.Close()
|
||||
}()
|
||||
select {
|
||||
case <-srv.Server.ReadyNotify():
|
||||
case <-time.After(3 * time.Second):
|
||||
t.Fatalf("failed to start embed.Etcd for creating snapshots")
|
||||
}
|
||||
|
||||
ccfg := clientv3.Config{Endpoints: []string{cfg.ACUrls[0].String()}}
|
||||
cli, err := clientv3.New(ccfg)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer cli.Close()
|
||||
for i := range kvs {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), testutil.RequestTimeout)
|
||||
_, err = cli.Put(ctx, kvs[i].k, kvs[i].v)
|
||||
cancel()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
sp := NewV3(zap.NewExample())
|
||||
dpPath := filepath.Join(os.TempDir(), fmt.Sprintf("snapshot%d.db", time.Now().Nanosecond()))
|
||||
if err = sp.Save(context.Background(), ccfg, dpPath); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
os.RemoveAll(cfg.Dir)
|
||||
srv.Close()
|
||||
return dpPath
|
||||
}
|
||||
|
||||
const testClusterTkn = "tkn"
|
||||
|
||||
func restoreCluster(t *testing.T, clusterN int, dbPath string) (
|
||||
cURLs []url.URL,
|
||||
pURLs []url.URL,
|
||||
srvs []*embed.Etcd) {
|
||||
urls := newEmbedURLs(clusterN * 2)
|
||||
cURLs, pURLs = urls[:clusterN], urls[clusterN:]
|
||||
|
||||
ics := ""
|
||||
for i := 0; i < clusterN; i++ {
|
||||
ics += fmt.Sprintf(",%d=%s", i, pURLs[i].String())
|
||||
}
|
||||
ics = ics[1:]
|
||||
|
||||
cfgs := make([]*embed.Config, clusterN)
|
||||
for i := 0; i < clusterN; i++ {
|
||||
cfg := embed.NewConfig()
|
||||
cfg.Logger = "zap"
|
||||
cfg.LogOutputs = []string{"/dev/null"}
|
||||
cfg.Name = fmt.Sprintf("%d", i)
|
||||
cfg.InitialClusterToken = testClusterTkn
|
||||
cfg.ClusterState = "existing"
|
||||
cfg.LCUrls, cfg.ACUrls = []url.URL{cURLs[i]}, []url.URL{cURLs[i]}
|
||||
cfg.LPUrls, cfg.APUrls = []url.URL{pURLs[i]}, []url.URL{pURLs[i]}
|
||||
cfg.InitialCluster = ics
|
||||
cfg.Dir = filepath.Join(os.TempDir(), fmt.Sprint(time.Now().Nanosecond()+i))
|
||||
|
||||
sp := NewV3(zap.NewExample())
|
||||
if err := sp.Restore(RestoreConfig{
|
||||
SnapshotPath: dbPath,
|
||||
Name: cfg.Name,
|
||||
OutputDataDir: cfg.Dir,
|
||||
PeerURLs: []string{pURLs[i].String()},
|
||||
InitialCluster: ics,
|
||||
InitialClusterToken: cfg.InitialClusterToken,
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
cfgs[i] = cfg
|
||||
}
|
||||
|
||||
sch := make(chan *embed.Etcd)
|
||||
for i := range cfgs {
|
||||
go func(idx int) {
|
||||
srv, err := embed.StartEtcd(cfgs[idx])
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
<-srv.Server.ReadyNotify()
|
||||
sch <- srv
|
||||
}(i)
|
||||
}
|
||||
|
||||
srvs = make([]*embed.Etcd, clusterN)
|
||||
for i := 0; i < clusterN; i++ {
|
||||
select {
|
||||
case srv := <-sch:
|
||||
srvs[i] = srv
|
||||
case <-time.After(5 * time.Second):
|
||||
t.Fatalf("#%d: failed to start embed.Etcd", i)
|
||||
}
|
||||
}
|
||||
return cURLs, pURLs, srvs
|
||||
}
|
||||
|
||||
// TODO: TLS
|
||||
func newEmbedURLs(n int) (urls []url.URL) {
|
||||
urls = make([]url.URL, n)
|
||||
for i := 0; i < n; i++ {
|
||||
rand.Seed(int64(time.Now().Nanosecond()))
|
||||
u, _ := url.Parse(fmt.Sprintf("unix://localhost:%d", rand.Intn(45000)))
|
||||
urls[i] = *u
|
||||
}
|
||||
return urls
|
||||
}
|
||||
Reference in New Issue
Block a user