mirror of
https://github.com/etcd-io/etcd.git
synced 2024-09-27 06:25:44 +00:00
Merge pull request #9198 from gyuho/snapshot-test
snapshot: add "snapshot" restore/save/member-add tests
This commit is contained in:
commit
131da29c63
21
pkg/testutil/var.go
Normal file
21
pkg/testutil/var.go
Normal file
@ -0,0 +1,21 @@
|
||||
// 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 testutil
|
||||
|
||||
import "time"
|
||||
|
||||
var (
|
||||
RequestTimeout = 3 * time.Second
|
||||
)
|
117
snapshot/member_test.go
Normal file
117
snapshot/member_test.go
Normal file
@ -0,0 +1,117 @@
|
||||
// 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"
|
||||
|
||||
"github.com/coreos/etcd/clientv3"
|
||||
"github.com/coreos/etcd/embed"
|
||||
"github.com/coreos/etcd/etcdserver"
|
||||
"github.com/coreos/etcd/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)
|
||||
}
|
||||
|
||||
cfg := embed.NewConfig()
|
||||
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)
|
||||
}
|
||||
for i := range kvs {
|
||||
var gresp *clientv3.GetResponse
|
||||
ctx, cancel = context.WithTimeout(context.Background(), testutil.RequestTimeout)
|
||||
gresp, err = cli2.Get(ctx, kvs[i].k)
|
||||
cancel()
|
||||
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))
|
||||
}
|
||||
}
|
||||
}
|
281
snapshot/v3_snapshot_test.go
Normal file
281
snapshot/v3_snapshot_test.go
Normal file
@ -0,0 +1,281 @@
|
||||
// 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"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/coreos/etcd/clientv3"
|
||||
"github.com/coreos/etcd/embed"
|
||||
"github.com/coreos/etcd/pkg/logger"
|
||||
"github.com/coreos/etcd/pkg/testutil"
|
||||
"github.com/coreos/etcd/pkg/types"
|
||||
|
||||
"github.com/coreos/pkg/capnslog"
|
||||
)
|
||||
|
||||
// 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.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(nil, logger.NewPackageLogger(capnslog.NewPackageLogger("github.com/coreos/etcd", "snapshot")))
|
||||
|
||||
err := sp.Restore(dbPath, RestoreConfig{})
|
||||
if err.Error() != `couldn't find local name "" in the initial cluster configuration` {
|
||||
t.Fatalf("expected restore error, got %v", err)
|
||||
}
|
||||
var iURLs types.URLsMap
|
||||
iURLs, err = types.NewURLsMap(cfg.InitialCluster)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err = sp.Restore(dbPath, RestoreConfig{
|
||||
Name: cfg.Name,
|
||||
OutputDataDir: cfg.Dir,
|
||||
InitialCluster: iURLs,
|
||||
InitialClusterToken: cfg.InitialClusterToken,
|
||||
PeerURLs: pURLs,
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var srv *embed.Etcd
|
||||
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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type kv struct {
|
||||
k, v string
|
||||
}
|
||||
|
||||
// creates a snapshot file and returns the file path.
|
||||
func createSnapshotFile(t *testing.T, kvs []kv) string {
|
||||
clusterN := 1
|
||||
urls := newEmbedURLs(clusterN * 2)
|
||||
cURLs, pURLs := urls[:clusterN], urls[clusterN:]
|
||||
|
||||
cfg := embed.NewConfig()
|
||||
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")
|
||||
}
|
||||
|
||||
cli, err := clientv3.New(clientv3.Config{Endpoints: []string{cfg.ACUrls[0].String()}})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
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(cli, logger.NewPackageLogger(capnslog.NewPackageLogger("github.com/coreos/etcd", "snapshot")))
|
||||
dpPath := filepath.Join(os.TempDir(), fmt.Sprintf("snapshot%d.db", time.Now().Nanosecond()))
|
||||
if err = sp.Save(context.Background(), 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:]
|
||||
iURLs, err := types.NewURLsMap(ics)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
cfgs := make([]*embed.Config, clusterN)
|
||||
for i := 0; i < clusterN; i++ {
|
||||
cfg := embed.NewConfig()
|
||||
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(nil, logger.NewPackageLogger(capnslog.NewPackageLogger("github.com/coreos/etcd", "snapshot")))
|
||||
if err := sp.Restore(dbPath, RestoreConfig{
|
||||
Name: cfg.Name,
|
||||
OutputDataDir: cfg.Dir,
|
||||
InitialCluster: iURLs,
|
||||
InitialClusterToken: cfg.InitialClusterToken,
|
||||
PeerURLs: types.URLs{pURLs[i]},
|
||||
}); 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.Fatal(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
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user