From fefb58dc90713f41cb791d3886cb210fd641b77f Mon Sep 17 00:00:00 2001 From: Gyu-Ho Lee Date: Thu, 14 Apr 2016 11:42:57 -0700 Subject: [PATCH] e2e: clean up, add more tests --- e2e/ctl_v3_auth_test.go | 32 +++ e2e/ctl_v3_defrag_test.go | 45 ++++ e2e/ctl_v3_endpoint_test.go | 54 +++++ e2e/ctl_v3_kv_test.go | 211 +++++++++++++++++ e2e/ctl_v3_member_test.go | 34 +++ e2e/ctl_v3_test.go | 447 ++---------------------------------- e2e/ctl_v3_txn_test.go | 129 +++++++++++ e2e/ctl_v3_user_test.go | 38 --- e2e/ctl_v3_watch_test.go | 110 +++++++++ e2e/etcd_test.go | 102 +------- e2e/v2_curl_test.go | 113 +++++++++ 11 files changed, 753 insertions(+), 562 deletions(-) create mode 100644 e2e/ctl_v3_auth_test.go create mode 100644 e2e/ctl_v3_defrag_test.go create mode 100644 e2e/ctl_v3_endpoint_test.go create mode 100644 e2e/ctl_v3_kv_test.go create mode 100644 e2e/ctl_v3_member_test.go create mode 100644 e2e/ctl_v3_txn_test.go create mode 100644 e2e/ctl_v3_watch_test.go create mode 100644 e2e/v2_curl_test.go diff --git a/e2e/ctl_v3_auth_test.go b/e2e/ctl_v3_auth_test.go new file mode 100644 index 000000000..5a0f9665a --- /dev/null +++ b/e2e/ctl_v3_auth_test.go @@ -0,0 +1,32 @@ +// 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 e2e + +import "testing" + +func TestCtlV3AuthEnable(t *testing.T) { testCtl(t, authEnableTest) } + +// TODO: test auth disable + +func authEnableTest(cx ctlCtx) { + if err := ctlV3AuthEnable(cx); err != nil { + cx.t.Fatalf("authEnableTest ctlV3AuthEnable error (%v)", err) + } +} + +func ctlV3AuthEnable(cx ctlCtx) error { + cmdArgs := append(cx.PrefixArgs(), "auth", "enable") + return spawnWithExpect(cmdArgs, "Authentication Enabled") +} diff --git a/e2e/ctl_v3_defrag_test.go b/e2e/ctl_v3_defrag_test.go new file mode 100644 index 000000000..bb3010020 --- /dev/null +++ b/e2e/ctl_v3_defrag_test.go @@ -0,0 +1,45 @@ +// Copyright 2016 CoreOS, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package e2e + +import "testing" + +func TestCtlV3Defrag(t *testing.T) { testCtl(t, defragTest) } + +func defragTest(cx ctlCtx) { + var kvs = []kv{{"key", "val1"}, {"key", "val2"}, {"key", "val3"}} + for i := range kvs { + if err := ctlV3Put(cx, kvs[i].key, kvs[i].val, ""); err != nil { + cx.t.Fatal(err) + } + } + + if err := ctlV3Compact(cx, 4); err != nil { + cx.t.Fatal(err) + } + + if err := ctlV3Defrag(cx); err != nil { + cx.t.Fatalf("defragTest ctlV3Defrag error (%v)", err) + } +} + +func ctlV3Defrag(cx ctlCtx) error { + cmdArgs := append(cx.PrefixArgs(), "defrag") + lines := make([]string, cx.epc.cfg.clusterSize) + for i := range lines { + lines[i] = "Finished defragmenting etcd member" + } + return spawnWithExpects(cmdArgs, lines...) +} diff --git a/e2e/ctl_v3_endpoint_test.go b/e2e/ctl_v3_endpoint_test.go new file mode 100644 index 000000000..444056e8a --- /dev/null +++ b/e2e/ctl_v3_endpoint_test.go @@ -0,0 +1,54 @@ +// 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 e2e + +import ( + "net/url" + "testing" +) + +func TestCtlV3EndpointHealth(t *testing.T) { testCtl(t, endpointHealthTest, withQuorum()) } +func TestCtlV3EndpointStatus(t *testing.T) { testCtl(t, endpointStatusTest, withQuorum()) } + +func endpointHealthTest(cx ctlCtx) { + if err := ctlV3EndpointHealth(cx); err != nil { + cx.t.Fatalf("endpointStatusTest ctlV3EndpointHealth error (%v)", err) + } +} + +func ctlV3EndpointHealth(cx ctlCtx) error { + cmdArgs := append(cx.PrefixArgs(), "endpoint", "health") + lines := make([]string, cx.epc.cfg.clusterSize) + for i := range lines { + lines[i] = "is healthy" + } + return spawnWithExpects(cmdArgs, lines...) +} + +func endpointStatusTest(cx ctlCtx) { + if err := ctlV3EndpointStatus(cx); err != nil { + cx.t.Fatalf("endpointStatusTest ctlV3EndpointStatus error (%v)", err) + } +} + +func ctlV3EndpointStatus(cx ctlCtx) error { + cmdArgs := append(cx.PrefixArgs(), "endpoint", "status") + var eps []string + for _, ep := range cx.epc.endpoints() { + u, _ := url.Parse(ep) + eps = append(eps, u.Host) + } + return spawnWithExpects(cmdArgs, eps...) +} diff --git a/e2e/ctl_v3_kv_test.go b/e2e/ctl_v3_kv_test.go new file mode 100644 index 000000000..143183045 --- /dev/null +++ b/e2e/ctl_v3_kv_test.go @@ -0,0 +1,211 @@ +// 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 e2e + +import ( + "fmt" + "testing" +) + +func TestCtlV3Put(t *testing.T) { testCtl(t, putTest) } +func TestCtlV3PutNoTLS(t *testing.T) { testCtl(t, putTest, withCfg(configNoTLS)) } +func TestCtlV3PutClientTLS(t *testing.T) { testCtl(t, putTest, withCfg(configClientTLS)) } +func TestCtlV3PutPeerTLS(t *testing.T) { testCtl(t, putTest, withCfg(configPeerTLS)) } +func TestCtlV3PutTimeout(t *testing.T) { testCtl(t, putTest, withDialTimeout(0)) } + +func TestCtlV3Get(t *testing.T) { testCtl(t, getTest) } +func TestCtlV3GetNoTLS(t *testing.T) { testCtl(t, getTest, withCfg(configNoTLS)) } +func TestCtlV3GetClientTLS(t *testing.T) { testCtl(t, getTest, withCfg(configClientTLS)) } +func TestCtlV3GetPeerTLS(t *testing.T) { testCtl(t, getTest, withCfg(configPeerTLS)) } +func TestCtlV3GetTimeout(t *testing.T) { testCtl(t, getTest, withDialTimeout(0)) } +func TestCtlV3GetQuorum(t *testing.T) { testCtl(t, getTest, withQuorum()) } + +func TestCtlV3GetFormat(t *testing.T) { testCtl(t, getFormatTest) } +func TestCtlV3GetRev(t *testing.T) { testCtl(t, getRevTest) } + +func TestCtlV3Del(t *testing.T) { testCtl(t, delTest) } +func TestCtlV3DelNoTLS(t *testing.T) { testCtl(t, delTest, withCfg(configNoTLS)) } +func TestCtlV3DelClientTLS(t *testing.T) { testCtl(t, delTest, withCfg(configClientTLS)) } +func TestCtlV3DelPeerTLS(t *testing.T) { testCtl(t, delTest, withCfg(configPeerTLS)) } +func TestCtlV3DelTimeout(t *testing.T) { testCtl(t, delTest, withDialTimeout(0)) } + +func putTest(cx ctlCtx) { + key, value := "foo", "bar" + + if err := ctlV3Put(cx, key, value, ""); err != nil { + if cx.dialTimeout > 0 && !isGRPCTimedout(err) { + cx.t.Fatalf("putTest ctlV3Put error (%v)", err) + } + } + if err := ctlV3Get(cx, []string{key}, kv{key, value}); err != nil { + if cx.dialTimeout > 0 && !isGRPCTimedout(err) { + cx.t.Fatalf("putTest ctlV3Get error (%v)", err) + } + } +} + +func getTest(cx ctlCtx) { + var ( + kvs = []kv{{"key1", "val1"}, {"key2", "val2"}, {"key3", "val3"}} + revkvs = []kv{{"key3", "val3"}, {"key2", "val2"}, {"key1", "val1"}} + ) + for i := range kvs { + if err := ctlV3Put(cx, kvs[i].key, kvs[i].val, ""); err != nil { + cx.t.Fatalf("getTest #%d: ctlV3Put error (%v)", i, err) + } + } + + tests := []struct { + args []string + + wkv []kv + }{ + {[]string{"key1"}, []kv{{"key1", "val1"}}}, + {[]string{"key", "--prefix"}, kvs}, + {[]string{"key", "--prefix", "--limit=2"}, kvs[:2]}, + {[]string{"key", "--prefix", "--order=ASCEND", "--sort-by=MODIFY"}, kvs}, + {[]string{"key", "--prefix", "--order=ASCEND", "--sort-by=VERSION"}, kvs}, + {[]string{"key", "--prefix", "--order=DESCEND", "--sort-by=CREATE"}, revkvs}, + {[]string{"key", "--prefix", "--order=DESCEND", "--sort-by=KEY"}, revkvs}, + } + for i, tt := range tests { + if err := ctlV3Get(cx, tt.args, tt.wkv...); err != nil { + if cx.dialTimeout > 0 && !isGRPCTimedout(err) { + cx.t.Errorf("getTest #%d: ctlV3Get error (%v)", i, err) + } + } + } +} + +func getFormatTest(cx ctlCtx) { + if err := ctlV3Put(cx, "abc", "123", ""); err != nil { + cx.t.Fatal(err) + } + + tests := []struct { + format string + + wstr string + }{ + {"simple", "abc"}, + {"json", "\"key\":\"YWJj\""}, + {"protobuf", "\x17\b\x93\xe7\xf6\x93\xd4ņ\xe14\x10\xed"}, + } + + for i, tt := range tests { + cmdArgs := append(cx.PrefixArgs(), "get") + cmdArgs = append(cmdArgs, "--write-out="+tt.format) + cmdArgs = append(cmdArgs, "abc") + if err := spawnWithExpect(cmdArgs, tt.wstr); err != nil { + cx.t.Errorf("#%d: error (%v), wanted %v", i, err, tt.wstr) + } + } +} + +func getRevTest(cx ctlCtx) { + var ( + kvs = []kv{{"key", "val1"}, {"key", "val2"}, {"key", "val3"}} + ) + for i := range kvs { + if err := ctlV3Put(cx, kvs[i].key, kvs[i].val, ""); err != nil { + cx.t.Fatalf("getRevTest #%d: ctlV3Put error (%v)", i, err) + } + } + + tests := []struct { + args []string + + wkv []kv + }{ + {[]string{"key", "--rev", "2"}, kvs[:1]}, + {[]string{"key", "--rev", "3"}, kvs[1:2]}, + {[]string{"key", "--rev", "4"}, kvs[2:]}, + } + + for i, tt := range tests { + if err := ctlV3Get(cx, tt.args, tt.wkv...); err != nil { + cx.t.Errorf("getTest #%d: ctlV3Get error (%v)", i, err) + } + } +} + +func delTest(cx ctlCtx) { + tests := []struct { + puts []kv + args []string + + deletedNum int + }{ + { + []kv{{"this", "value"}}, + []string{"that"}, + 0, + }, + { + []kv{{"sample", "value"}}, + []string{"sample"}, + 1, + }, + { + []kv{{"key1", "val1"}, {"key2", "val2"}, {"key3", "val3"}}, + []string{"key", "--prefix"}, + 3, + }, + } + + for i, tt := range tests { + for j := range tt.puts { + if err := ctlV3Put(cx, tt.puts[j].key, tt.puts[j].val, ""); err != nil { + cx.t.Fatalf("delTest #%d-%d: ctlV3Put error (%v)", i, j, err) + } + } + if err := ctlV3Del(cx, tt.args, tt.deletedNum); err != nil { + if cx.dialTimeout > 0 && !isGRPCTimedout(err) { + cx.t.Fatalf("delTest #%d: ctlV3Del error (%v)", i, err) + } + } + } +} + +func ctlV3Put(cx ctlCtx, key, value, leaseID string) error { + cmdArgs := append(cx.PrefixArgs(), "put", key, value) + if leaseID != "" { + cmdArgs = append(cmdArgs, "--lease", leaseID) + } + return spawnWithExpect(cmdArgs, "OK") +} + +type kv struct { + key, val string +} + +func ctlV3Get(cx ctlCtx, args []string, kvs ...kv) error { + cmdArgs := append(cx.PrefixArgs(), "get") + cmdArgs = append(cmdArgs, args...) + if !cx.quorum { + cmdArgs = append(cmdArgs, "--consistency", "s") + } + var lines []string + for _, elem := range kvs { + lines = append(lines, elem.key, elem.val) + } + return spawnWithExpects(cmdArgs, lines...) +} + +func ctlV3Del(cx ctlCtx, args []string, num int) error { + cmdArgs := append(cx.PrefixArgs(), "del") + cmdArgs = append(cmdArgs, args...) + return spawnWithExpects(cmdArgs, fmt.Sprintf("%d", num)) +} diff --git a/e2e/ctl_v3_member_test.go b/e2e/ctl_v3_member_test.go new file mode 100644 index 000000000..6d51c12ae --- /dev/null +++ b/e2e/ctl_v3_member_test.go @@ -0,0 +1,34 @@ +// 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 e2e + +import "testing" + +func TestCtlV3MemberList(t *testing.T) { testCtl(t, memberListTest) } + +func memberListTest(cx ctlCtx) { + if err := ctlV3MemberList(cx); err != nil { + cx.t.Fatalf("memberListTest ctlV3MemberList error (%v)", err) + } +} + +func ctlV3MemberList(cx ctlCtx) error { + cmdArgs := append(cx.PrefixArgs(), "member", "list") + lines := make([]string, cx.cfg.clusterSize) + for i := range lines { + lines[i] = "started" + } + return spawnWithExpects(cmdArgs, lines...) +} diff --git a/e2e/ctl_v3_test.go b/e2e/ctl_v3_test.go index fbb242822..ac9d220bb 100644 --- a/e2e/ctl_v3_test.go +++ b/e2e/ctl_v3_test.go @@ -15,7 +15,6 @@ package e2e import ( - "fmt" "os" "strings" "testing" @@ -25,64 +24,18 @@ import ( "github.com/coreos/etcd/version" ) -func TestCtlV3Put(t *testing.T) { testCtl(t, putTest) } -func TestCtlV3PutNoTLS(t *testing.T) { testCtl(t, putTest, withCfg(configNoTLS)) } -func TestCtlV3PutClientTLS(t *testing.T) { testCtl(t, putTest, withCfg(configClientTLS)) } -func TestCtlV3PutPeerTLS(t *testing.T) { testCtl(t, putTest, withCfg(configPeerTLS)) } -func TestCtlV3PutTimeout(t *testing.T) { testCtl(t, putTest, withDialTimeout(0)) } +func TestCtlV3Version(t *testing.T) { testCtl(t, versionTest) } -func TestCtlV3Get(t *testing.T) { testCtl(t, getTest) } -func TestCtlV3GetNoTLS(t *testing.T) { testCtl(t, getTest, withCfg(configNoTLS)) } -func TestCtlV3GetClientTLS(t *testing.T) { testCtl(t, getTest, withCfg(configClientTLS)) } -func TestCtlV3GetPeerTLS(t *testing.T) { testCtl(t, getTest, withCfg(configPeerTLS)) } -func TestCtlV3GetTimeout(t *testing.T) { testCtl(t, getTest, withDialTimeout(0)) } -func TestCtlV3GetQuorum(t *testing.T) { testCtl(t, getTest, withQuorum()) } - -func TestCtlV3GetFormat(t *testing.T) { testCtl(t, getFormatTest) } -func TestCtlV3GetRev(t *testing.T) { testCtl(t, getRevTest) } - -func TestCtlV3Del(t *testing.T) { testCtl(t, delTest) } -func TestCtlV3DelNoTLS(t *testing.T) { testCtl(t, delTest, withCfg(configNoTLS)) } -func TestCtlV3DelClientTLS(t *testing.T) { testCtl(t, delTest, withCfg(configClientTLS)) } -func TestCtlV3DelPeerTLS(t *testing.T) { testCtl(t, delTest, withCfg(configPeerTLS)) } -func TestCtlV3DelTimeout(t *testing.T) { testCtl(t, delTest, withDialTimeout(0)) } - -func TestCtlV3Watch(t *testing.T) { testCtl(t, watchTest) } -func TestCtlV3WatchNoTLS(t *testing.T) { testCtl(t, watchTest, withCfg(configNoTLS)) } -func TestCtlV3WatchClientTLS(t *testing.T) { testCtl(t, watchTest, withCfg(configClientTLS)) } -func TestCtlV3WatchPeerTLS(t *testing.T) { testCtl(t, watchTest, withCfg(configPeerTLS)) } -func TestCtlV3WatchTimeout(t *testing.T) { testCtl(t, watchTest, withDialTimeout(0)) } -func TestCtlV3WatchInteractive(t *testing.T) { - testCtl(t, watchTest, withInteractive()) -} -func TestCtlV3WatchInteractiveNoTLS(t *testing.T) { - testCtl(t, watchTest, withInteractive(), withCfg(configNoTLS)) -} -func TestCtlV3WatchInteractiveClientTLS(t *testing.T) { - testCtl(t, watchTest, withInteractive(), withCfg(configClientTLS)) -} -func TestCtlV3WatchInteractivePeerTLS(t *testing.T) { - testCtl(t, watchTest, withInteractive(), withCfg(configPeerTLS)) +func versionTest(cx ctlCtx) { + if err := ctlV3Version(cx); err != nil { + cx.t.Fatalf("versionTest ctlV3Version error (%v)", err) + } } -func TestCtlV3TxnInteractiveSuccess(t *testing.T) { - testCtl(t, txnTestSuccess, withInteractive()) +func ctlV3Version(cx ctlCtx) error { + cmdArgs := append(cx.PrefixArgs(), "version") + return spawnWithExpect(cmdArgs, version.Version) } -func TestCtlV3TxnInteractiveSuccessNoTLS(t *testing.T) { - testCtl(t, txnTestSuccess, withInteractive(), withCfg(configNoTLS)) -} -func TestCtlV3TxnInteractiveSuccessClientTLS(t *testing.T) { - testCtl(t, txnTestSuccess, withInteractive(), withCfg(configClientTLS)) -} -func TestCtlV3TxnInteractiveSuccessPeerTLS(t *testing.T) { - testCtl(t, txnTestSuccess, withInteractive(), withCfg(configPeerTLS)) -} -func TestCtlV3TxnInteractiveFail(t *testing.T) { - testCtl(t, txnTestFail, withInteractive()) -} - -func TestCtlV3Version(t *testing.T) { testCtl(t, versionTest) } -func TestCtlV3EpHealthQuorum(t *testing.T) { testCtl(t, epHealthTest, withQuorum()) } type ctlCtx struct { t *testing.T @@ -119,18 +72,6 @@ func withInteractive() ctlOption { return func(cx *ctlCtx) { cx.interactive = true } } -func setupCtlV3Test(t *testing.T, cfg etcdProcessClusterConfig, quorum bool) *etcdProcessCluster { - mustEtcdctl(t) - if !quorum { - cfg = *configStandalone(cfg) - } - epc, err := newEtcdProcessCluster(&cfg) - if err != nil { - t.Fatalf("could not start etcd process cluster (%v)", err) - } - return epc -} - func testCtl(t *testing.T, testFunc func(ctlCtx), opts ...ctlOption) { defer testutil.AfterTest(t) @@ -142,7 +83,15 @@ func testCtl(t *testing.T, testFunc func(ctlCtx), opts ...ctlOption) { ret.applyOpts(opts) os.Setenv("ETCDCTL_API", "3") - ret.epc = setupCtlV3Test(ret.t, ret.cfg, ret.quorum) + mustEtcdctl(t) + if !ret.quorum { + ret.cfg = *configStandalone(ret.cfg) + } + epc, err := newEtcdProcessCluster(&ret.cfg) + if err != nil { + t.Fatalf("could not start etcd process cluster (%v)", err) + } + ret.epc = epc defer func() { os.Unsetenv("ETCDCTL_API") @@ -166,227 +115,6 @@ func testCtl(t *testing.T, testFunc func(ctlCtx), opts ...ctlOption) { } } -func putTest(cx ctlCtx) { - key, value := "foo", "bar" - - if err := ctlV3Put(cx, key, value, ""); err != nil { - if cx.dialTimeout > 0 && !isGRPCTimedout(err) { - cx.t.Fatalf("putTest ctlV3Put error (%v)", err) - } - } - if err := ctlV3Get(cx, []string{key}, kv{key, value}); err != nil { - if cx.dialTimeout > 0 && !isGRPCTimedout(err) { - cx.t.Fatalf("putTest ctlV3Get error (%v)", err) - } - } -} - -func getTest(cx ctlCtx) { - var ( - kvs = []kv{{"key1", "val1"}, {"key2", "val2"}, {"key3", "val3"}} - revkvs = []kv{{"key3", "val3"}, {"key2", "val2"}, {"key1", "val1"}} - ) - for i := range kvs { - if err := ctlV3Put(cx, kvs[i].key, kvs[i].val, ""); err != nil { - cx.t.Fatalf("getTest #%d: ctlV3Put error (%v)", i, err) - } - } - - tests := []struct { - args []string - - wkv []kv - }{ - {[]string{"key1"}, []kv{{"key1", "val1"}}}, - {[]string{"key", "--prefix"}, kvs}, - {[]string{"key", "--prefix", "--limit=2"}, kvs[:2]}, - {[]string{"key", "--prefix", "--order=ASCEND", "--sort-by=MODIFY"}, kvs}, - {[]string{"key", "--prefix", "--order=ASCEND", "--sort-by=VERSION"}, kvs}, - {[]string{"key", "--prefix", "--order=DESCEND", "--sort-by=CREATE"}, revkvs}, - {[]string{"key", "--prefix", "--order=DESCEND", "--sort-by=KEY"}, revkvs}, - } - for i, tt := range tests { - if err := ctlV3Get(cx, tt.args, tt.wkv...); err != nil { - if cx.dialTimeout > 0 && !isGRPCTimedout(err) { - cx.t.Errorf("getTest #%d: ctlV3Get error (%v)", i, err) - } - } - } -} - -func getFormatTest(cx ctlCtx) { - if err := ctlV3Put(cx, "abc", "123", ""); err != nil { - cx.t.Fatal(err) - } - - tests := []struct { - format string - - wstr string - }{ - {"simple", "abc"}, - {"json", "\"key\":\"YWJj\""}, - {"protobuf", "\x17\b\x93\xe7\xf6\x93\xd4ņ\xe14\x10\xed"}, - } - - for i, tt := range tests { - cmdArgs := append(cx.PrefixArgs(), "get") - cmdArgs = append(cmdArgs, "--write-out="+tt.format) - cmdArgs = append(cmdArgs, "abc") - if err := spawnWithExpect(cmdArgs, tt.wstr); err != nil { - cx.t.Errorf("#%d: error (%v), wanted %v", i, err, tt.wstr) - } - } -} - -func getRevTest(cx ctlCtx) { - var ( - kvs = []kv{{"key", "val1"}, {"key", "val2"}, {"key", "val3"}} - ) - for i := range kvs { - if err := ctlV3Put(cx, kvs[i].key, kvs[i].val, ""); err != nil { - cx.t.Fatalf("getRevTest #%d: ctlV3Put error (%v)", i, err) - } - } - - tests := []struct { - args []string - - wkv []kv - }{ - {[]string{"key", "--rev", "2"}, kvs[:1]}, - {[]string{"key", "--rev", "3"}, kvs[1:2]}, - {[]string{"key", "--rev", "4"}, kvs[2:]}, - } - - for i, tt := range tests { - if err := ctlV3Get(cx, tt.args, tt.wkv...); err != nil { - cx.t.Errorf("getTest #%d: ctlV3Get error (%v)", i, err) - } - } -} - -func delTest(cx ctlCtx) { - tests := []struct { - puts []kv - args []string - - deletedNum int - }{ - { - []kv{{"this", "value"}}, - []string{"that"}, - 0, - }, - { - []kv{{"sample", "value"}}, - []string{"sample"}, - 1, - }, - { - []kv{{"key1", "val1"}, {"key2", "val2"}, {"key3", "val3"}}, - []string{"key", "--prefix"}, - 3, - }, - } - - for i, tt := range tests { - for j := range tt.puts { - if err := ctlV3Put(cx, tt.puts[j].key, tt.puts[j].val, ""); err != nil { - cx.t.Fatalf("delTest #%d-%d: ctlV3Put error (%v)", i, j, err) - } - } - if err := ctlV3Del(cx, tt.args, tt.deletedNum); err != nil { - if cx.dialTimeout > 0 && !isGRPCTimedout(err) { - cx.t.Fatalf("delTest #%d: ctlV3Del error (%v)", i, err) - } - } - } -} - -func watchTest(cx ctlCtx) { - tests := []struct { - puts []kv - args []string - - wkv []kv - }{ - { - []kv{{"sample", "value"}}, - []string{"sample", "--rev", "1"}, - []kv{{"sample", "value"}}, - }, - { - []kv{{"key1", "val1"}, {"key2", "val2"}, {"key3", "val3"}}, - []string{"key", "--rev", "1", "--prefix"}, - []kv{{"key1", "val1"}, {"key2", "val2"}, {"key3", "val3"}}, - }, - { - []kv{{"etcd", "revision_1"}, {"etcd", "revision_2"}, {"etcd", "revision_3"}}, - []string{"etcd", "--rev", "2"}, - []kv{{"etcd", "revision_2"}, {"etcd", "revision_3"}}, - }, - } - - for i, tt := range tests { - go func() { - for j := range tt.puts { - if err := ctlV3Put(cx, tt.puts[j].key, tt.puts[j].val, ""); err != nil { - cx.t.Fatalf("watchTest #%d-%d: ctlV3Put error (%v)", i, j, err) - } - } - }() - if err := ctlV3Watch(cx, tt.args, tt.wkv...); err != nil { - if cx.dialTimeout > 0 && !isGRPCTimedout(err) { - cx.t.Errorf("watchTest #%d: ctlV3Watch error (%v)", i, err) - } - } - } -} - -func versionTest(cx ctlCtx) { - if err := ctlV3Version(cx); err != nil { - cx.t.Fatalf("versionTest ctlV3Version error (%v)", err) - } -} - -func epHealthTest(cx ctlCtx) { - if err := ctlV3EpHealth(cx); err != nil { - cx.t.Fatalf("epHealthTest ctlV3EpHealth error (%v)", err) - } -} - -func txnTestSuccess(cx ctlCtx) { - if err := ctlV3Put(cx, "key1", "value1", ""); err != nil { - cx.t.Fatalf("txnTestSuccess ctlV3Put error (%v)", err) - } - if err := ctlV3Put(cx, "key2", "value2", ""); err != nil { - cx.t.Fatalf("txnTestSuccess ctlV3Put error (%v)", err) - } - - rqs := txnRequests{ - compare: []string{`version("key1") = "1"`, `version("key2") = "1"`}, - ifSucess: []string{"get key1", "get key2"}, - ifFail: []string{`put key1 "fail"`, `put key2 "fail"`}, - results: []string{"SUCCESS", "key1", "value1", "key2", "value2"}, - } - if err := ctlV3Txn(cx, rqs); err != nil { - cx.t.Fatal(err) - } -} - -func txnTestFail(cx ctlCtx) { - rqs := txnRequests{ - compare: []string{`version("key") < "0"`}, - ifSucess: []string{`put key "success"`}, - ifFail: []string{`put key "fail"`}, - results: []string{"FAILURE", "OK"}, - } - if err := ctlV3Txn(cx, rqs); err != nil { - cx.t.Fatal(err) - } -} - func (cx *ctlCtx) PrefixArgs() []string { if len(cx.epc.proxies()) > 0 { // TODO: add proxy check as in v2 panic("v3 proxy not implemented") @@ -407,147 +135,6 @@ func (cx *ctlCtx) PrefixArgs() []string { return cmdArgs } -func ctlV3Put(cx ctlCtx, key, value, leaseID string) error { - cmdArgs := append(cx.PrefixArgs(), "put", key, value) - if leaseID != "" { - cmdArgs = append(cmdArgs, "--lease", leaseID) - } - return spawnWithExpect(cmdArgs, "OK") -} - -type kv struct { - key, val string -} - -func ctlV3Get(cx ctlCtx, args []string, kvs ...kv) error { - cmdArgs := append(cx.PrefixArgs(), "get") - cmdArgs = append(cmdArgs, args...) - if !cx.quorum { - cmdArgs = append(cmdArgs, "--consistency", "s") - } - var lines []string - for _, elem := range kvs { - lines = append(lines, elem.key, elem.val) - } - return spawnWithExpects(cmdArgs, lines...) -} - -func ctlV3Del(cx ctlCtx, args []string, num int) error { - cmdArgs := append(cx.PrefixArgs(), "del") - cmdArgs = append(cmdArgs, args...) - return spawnWithExpects(cmdArgs, fmt.Sprintf("%d", num)) -} - -func ctlV3Watch(cx ctlCtx, args []string, kvs ...kv) error { - cmdArgs := append(cx.PrefixArgs(), "watch") - if cx.interactive { - cmdArgs = append(cmdArgs, "--interactive") - } else { - cmdArgs = append(cmdArgs, args...) - } - - proc, err := spawnCmd(cmdArgs) - if err != nil { - return err - } - - if cx.interactive { - wl := strings.Join(append([]string{"watch"}, args...), " ") + "\r" - if err = proc.Send(wl); err != nil { - return err - } - } - - for _, elem := range kvs { - if _, err = proc.Expect(elem.key); err != nil { - return err - } - if _, err = proc.Expect(elem.val); err != nil { - return err - } - } - return proc.Stop() -} - -type txnRequests struct { - compare []string - ifSucess []string - ifFail []string - results []string -} - -func ctlV3Txn(cx ctlCtx, rqs txnRequests) error { - // TODO: support non-interactive mode - cmdArgs := append(cx.PrefixArgs(), "txn") - if cx.interactive { - cmdArgs = append(cmdArgs, "--interactive") - } - proc, err := spawnCmd(cmdArgs) - if err != nil { - return err - } - _, err = proc.Expect("compares:") - if err != nil { - return err - } - for _, req := range rqs.compare { - if err = proc.Send(req + "\r"); err != nil { - return err - } - } - if err = proc.Send("\r"); err != nil { - return err - } - - _, err = proc.Expect("success requests (get, put, delete):") - if err != nil { - return err - } - for _, req := range rqs.ifSucess { - if err = proc.Send(req + "\r"); err != nil { - return err - } - } - if err = proc.Send("\r"); err != nil { - return err - } - - _, err = proc.Expect("failure requests (get, put, delete):") - if err != nil { - return err - } - for _, req := range rqs.ifFail { - if err = proc.Send(req + "\r"); err != nil { - return err - } - } - if err = proc.Send("\r"); err != nil { - return err - } - - for _, line := range rqs.results { - _, err = proc.Expect(line) - if err != nil { - return err - } - } - return proc.Close() -} - -func ctlV3Version(cx ctlCtx) error { - cmdArgs := append(cx.PrefixArgs(), "version") - return spawnWithExpect(cmdArgs, version.Version) -} - -func ctlV3EpHealth(cx ctlCtx) error { - cmdArgs := append(cx.PrefixArgs(), "endpoint", "health") - lines := make([]string, cx.epc.cfg.clusterSize) - for i := range lines { - lines[i] = "is healthy" - } - return spawnWithExpects(cmdArgs, lines...) -} - func isGRPCTimedout(err error) bool { return strings.Contains(err.Error(), "grpc: timed out trying to connect") } diff --git a/e2e/ctl_v3_txn_test.go b/e2e/ctl_v3_txn_test.go new file mode 100644 index 000000000..f7ae992a2 --- /dev/null +++ b/e2e/ctl_v3_txn_test.go @@ -0,0 +1,129 @@ +// 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 e2e + +import "testing" + +func TestCtlV3TxnInteractiveSuccess(t *testing.T) { + testCtl(t, txnTestSuccess, withInteractive()) +} +func TestCtlV3TxnInteractiveSuccessNoTLS(t *testing.T) { + testCtl(t, txnTestSuccess, withInteractive(), withCfg(configNoTLS)) +} +func TestCtlV3TxnInteractiveSuccessClientTLS(t *testing.T) { + testCtl(t, txnTestSuccess, withInteractive(), withCfg(configClientTLS)) +} +func TestCtlV3TxnInteractiveSuccessPeerTLS(t *testing.T) { + testCtl(t, txnTestSuccess, withInteractive(), withCfg(configPeerTLS)) +} +func TestCtlV3TxnInteractiveFail(t *testing.T) { + testCtl(t, txnTestFail, withInteractive()) +} + +func txnTestSuccess(cx ctlCtx) { + if err := ctlV3Put(cx, "key1", "value1", ""); err != nil { + cx.t.Fatalf("txnTestSuccess ctlV3Put error (%v)", err) + } + if err := ctlV3Put(cx, "key2", "value2", ""); err != nil { + cx.t.Fatalf("txnTestSuccess ctlV3Put error (%v)", err) + } + + rqs := txnRequests{ + compare: []string{`version("key1") = "1"`, `version("key2") = "1"`}, + ifSucess: []string{"get key1", "get key2"}, + ifFail: []string{`put key1 "fail"`, `put key2 "fail"`}, + results: []string{"SUCCESS", "key1", "value1", "key2", "value2"}, + } + if err := ctlV3Txn(cx, rqs); err != nil { + cx.t.Fatal(err) + } +} + +func txnTestFail(cx ctlCtx) { + rqs := txnRequests{ + compare: []string{`version("key") < "0"`}, + ifSucess: []string{`put key "success"`}, + ifFail: []string{`put key "fail"`}, + results: []string{"FAILURE", "OK"}, + } + if err := ctlV3Txn(cx, rqs); err != nil { + cx.t.Fatal(err) + } +} + +type txnRequests struct { + compare []string + ifSucess []string + ifFail []string + results []string +} + +func ctlV3Txn(cx ctlCtx, rqs txnRequests) error { + // TODO: support non-interactive mode + cmdArgs := append(cx.PrefixArgs(), "txn") + if cx.interactive { + cmdArgs = append(cmdArgs, "--interactive") + } + proc, err := spawnCmd(cmdArgs) + if err != nil { + return err + } + _, err = proc.Expect("compares:") + if err != nil { + return err + } + for _, req := range rqs.compare { + if err = proc.Send(req + "\r"); err != nil { + return err + } + } + if err = proc.Send("\r"); err != nil { + return err + } + + _, err = proc.Expect("success requests (get, put, delete):") + if err != nil { + return err + } + for _, req := range rqs.ifSucess { + if err = proc.Send(req + "\r"); err != nil { + return err + } + } + if err = proc.Send("\r"); err != nil { + return err + } + + _, err = proc.Expect("failure requests (get, put, delete):") + if err != nil { + return err + } + for _, req := range rqs.ifFail { + if err = proc.Send(req + "\r"); err != nil { + return err + } + } + if err = proc.Send("\r"); err != nil { + return err + } + + for _, line := range rqs.results { + _, err = proc.Expect(line) + if err != nil { + return err + } + } + return proc.Close() +} diff --git a/e2e/ctl_v3_user_test.go b/e2e/ctl_v3_user_test.go index 5ce1098ce..666e24487 100644 --- a/e2e/ctl_v3_user_test.go +++ b/e2e/ctl_v3_user_test.go @@ -24,7 +24,6 @@ func TestCtlV3UserAddTimeout(t *testing.T) { testCtl(t, userAddTest, withDialT func TestCtlV3UserDelete(t *testing.T) { testCtl(t, userDelTest) } func TestCtlV3UserPasswd(t *testing.T) { testCtl(t, userPasswdTest) } -func TestCtlV3UserGrant(t *testing.T) { testCtl(t, userGrantTest) } type userCmdDesc struct { args []string @@ -107,43 +106,6 @@ func userPasswdTest(cx ctlCtx) { } } -func userGrantTest(cx ctlCtx) { - // Add a role. - if err := ctlV3Role(cx, []string{"add", "root"}, "Role root created"); err != nil { - cx.t.Fatalf("userGrantTest: ctlV3Role error (%v)", err) - } - - cmdSet := []userCmdDesc{ - // Add a user name. - { - args: []string{"add", "username", "--interactive=false"}, - expectedStr: "User username created", - stdIn: []string{"password"}, - }, - // Grant the previously added user a role. - { - args: []string{"grant", "username", "root"}, - expectedStr: "Role root is granted to user username", - }, - // Supply a wrong user name. - { - args: []string{"grant", "username1", "root"}, - expectedStr: "user name not found", - }, - // Supply a wrong role name. - { - args: []string{"grant", "username", "root1"}, - expectedStr: "role name not found", - }, - } - - for i, cmd := range cmdSet { - if err := ctlV3User(cx, cmd.args, cmd.expectedStr, cmd.stdIn); err != nil { - cx.t.Fatalf("userPasswdTest #%d: ctlV3User error (%v)", i, err) - } - } -} - func ctlV3User(cx ctlCtx, args []string, expStr string, stdIn []string) error { cmdArgs := append(cx.PrefixArgs(), "user") cmdArgs = append(cmdArgs, args...) diff --git a/e2e/ctl_v3_watch_test.go b/e2e/ctl_v3_watch_test.go new file mode 100644 index 000000000..1601ff516 --- /dev/null +++ b/e2e/ctl_v3_watch_test.go @@ -0,0 +1,110 @@ +// 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 e2e + +import ( + "strings" + "testing" +) + +func TestCtlV3Watch(t *testing.T) { testCtl(t, watchTest) } +func TestCtlV3WatchNoTLS(t *testing.T) { testCtl(t, watchTest, withCfg(configNoTLS)) } +func TestCtlV3WatchClientTLS(t *testing.T) { testCtl(t, watchTest, withCfg(configClientTLS)) } +func TestCtlV3WatchPeerTLS(t *testing.T) { testCtl(t, watchTest, withCfg(configPeerTLS)) } +func TestCtlV3WatchTimeout(t *testing.T) { testCtl(t, watchTest, withDialTimeout(0)) } + +func TestCtlV3WatchInteractive(t *testing.T) { + testCtl(t, watchTest, withInteractive()) +} +func TestCtlV3WatchInteractiveNoTLS(t *testing.T) { + testCtl(t, watchTest, withInteractive(), withCfg(configNoTLS)) +} +func TestCtlV3WatchInteractiveClientTLS(t *testing.T) { + testCtl(t, watchTest, withInteractive(), withCfg(configClientTLS)) +} +func TestCtlV3WatchInteractivePeerTLS(t *testing.T) { + testCtl(t, watchTest, withInteractive(), withCfg(configPeerTLS)) +} + +func watchTest(cx ctlCtx) { + tests := []struct { + puts []kv + args []string + + wkv []kv + }{ + { + []kv{{"sample", "value"}}, + []string{"sample", "--rev", "1"}, + []kv{{"sample", "value"}}, + }, + { + []kv{{"key1", "val1"}, {"key2", "val2"}, {"key3", "val3"}}, + []string{"key", "--rev", "1", "--prefix"}, + []kv{{"key1", "val1"}, {"key2", "val2"}, {"key3", "val3"}}, + }, + { + []kv{{"etcd", "revision_1"}, {"etcd", "revision_2"}, {"etcd", "revision_3"}}, + []string{"etcd", "--rev", "2"}, + []kv{{"etcd", "revision_2"}, {"etcd", "revision_3"}}, + }, + } + + for i, tt := range tests { + go func() { + for j := range tt.puts { + if err := ctlV3Put(cx, tt.puts[j].key, tt.puts[j].val, ""); err != nil { + cx.t.Fatalf("watchTest #%d-%d: ctlV3Put error (%v)", i, j, err) + } + } + }() + if err := ctlV3Watch(cx, tt.args, tt.wkv...); err != nil { + if cx.dialTimeout > 0 && !isGRPCTimedout(err) { + cx.t.Errorf("watchTest #%d: ctlV3Watch error (%v)", i, err) + } + } + } +} + +func ctlV3Watch(cx ctlCtx, args []string, kvs ...kv) error { + cmdArgs := append(cx.PrefixArgs(), "watch") + if cx.interactive { + cmdArgs = append(cmdArgs, "--interactive") + } else { + cmdArgs = append(cmdArgs, args...) + } + + proc, err := spawnCmd(cmdArgs) + if err != nil { + return err + } + + if cx.interactive { + wl := strings.Join(append([]string{"watch"}, args...), " ") + "\r" + if err = proc.Send(wl); err != nil { + return err + } + } + + for _, elem := range kvs { + if _, err = proc.Expect(elem.key); err != nil { + return err + } + if _, err = proc.Expect(elem.val); err != nil { + return err + } + } + return proc.Stop() +} diff --git a/e2e/etcd_test.go b/e2e/etcd_test.go index 39487a167..42993b2be 100644 --- a/e2e/etcd_test.go +++ b/e2e/etcd_test.go @@ -17,15 +17,12 @@ package e2e import ( "fmt" "io/ioutil" - "math/rand" "net/url" "os" "strings" - "testing" "github.com/coreos/etcd/pkg/expect" "github.com/coreos/etcd/pkg/fileutil" - "github.com/coreos/etcd/pkg/testutil" ) const ( @@ -110,97 +107,6 @@ func configStandalone(cfg etcdProcessClusterConfig) *etcdProcessClusterConfig { return &ret } -func TestBasicOpsNoTLS(t *testing.T) { testBasicOpsPutGet(t, &configNoTLS) } -func TestBasicOpsAutoTLS(t *testing.T) { testBasicOpsPutGet(t, &configAutoTLS) } -func TestBasicOpsAllTLS(t *testing.T) { testBasicOpsPutGet(t, &configTLS) } -func TestBasicOpsPeerTLS(t *testing.T) { testBasicOpsPutGet(t, &configPeerTLS) } -func TestBasicOpsClientTLS(t *testing.T) { testBasicOpsPutGet(t, &configClientTLS) } -func TestBasicOpsProxyNoTLS(t *testing.T) { testBasicOpsPutGet(t, &configWithProxy) } -func TestBasicOpsProxyTLS(t *testing.T) { testBasicOpsPutGet(t, &configWithProxyTLS) } -func TestBasicOpsProxyPeerTLS(t *testing.T) { testBasicOpsPutGet(t, &configWithProxyPeerTLS) } -func TestBasicOpsClientBoth(t *testing.T) { testBasicOpsPutGet(t, &configClientBoth) } - -func testBasicOpsPutGet(t *testing.T, cfg *etcdProcessClusterConfig) { - defer testutil.AfterTest(t) - - // test doesn't use quorum gets, so ensure there are no followers to avoid - // stale reads that will break the test - cfg = configStandalone(*cfg) - - epc, err := newEtcdProcessCluster(cfg) - if err != nil { - t.Fatalf("could not start etcd process cluster (%v)", err) - } - defer func() { - if err := epc.Close(); err != nil { - t.Fatalf("error closing etcd processes (%v)", err) - } - }() - - expectPut := `{"action":"set","node":{"key":"/testKey","value":"foo","` - expectGet := `{"action":"get","node":{"key":"/testKey","value":"foo","` - - if cfg.clientTLS == clientTLSAndNonTLS { - if err := cURLPut(epc, "testKey", "foo", expectPut); err != nil { - t.Fatalf("failed put with curl (%v)", err) - } - - if err := cURLGet(epc, "testKey", expectGet); err != nil { - t.Fatalf("failed get with curl (%v)", err) - } - if err := cURLGetUseTLS(epc, "testKey", expectGet); err != nil { - t.Fatalf("failed get with curl (%v)", err) - } - } else { - if err := cURLPut(epc, "testKey", "foo", expectPut); err != nil { - t.Fatalf("failed put with curl (%v)", err) - } - - if err := cURLGet(epc, "testKey", expectGet); err != nil { - t.Fatalf("failed get with curl (%v)", err) - } - } -} - -// cURLPrefixArgs builds the beginning of a curl command for a given key -// addressed to a random URL in the given cluster. -func cURLPrefixArgs(clus *etcdProcessCluster, key string) []string { - cmdArgs := []string{"curl"} - acurl := clus.procs[rand.Intn(clus.cfg.clusterSize)].cfg.acurl - - if clus.cfg.clientTLS == clientTLS { - cmdArgs = append(cmdArgs, "--cacert", caPath, "--cert", certPath, "--key", privateKeyPath) - } - keyURL := acurl + "/v2/keys/testKey" - cmdArgs = append(cmdArgs, "-L", keyURL) - return cmdArgs -} - -func cURLPrefixArgsUseTLS(clus *etcdProcessCluster, key string) []string { - cmdArgs := []string{"curl"} - if clus.cfg.clientTLS != clientTLSAndNonTLS { - panic("should not use cURLPrefixArgsUseTLS when serving only TLS or non-TLS") - } - cmdArgs = append(cmdArgs, "--cacert", caPath, "--cert", certPath, "--key", privateKeyPath) - acurl := clus.procs[rand.Intn(clus.cfg.clusterSize)].cfg.acurltls - keyURL := acurl + "/v2/keys/testKey" - cmdArgs = append(cmdArgs, "-L", keyURL) - return cmdArgs -} - -func cURLPut(clus *etcdProcessCluster, key, val, expected string) error { - args := append(cURLPrefixArgs(clus, key), "-XPUT", "-d", "value="+val) - return spawnWithExpect(args, expected) -} - -func cURLGet(clus *etcdProcessCluster, key, expected string) error { - return spawnWithExpect(cURLPrefixArgs(clus, key), expected) -} - -func cURLGetUseTLS(clus *etcdProcessCluster, key, expected string) error { - return spawnWithExpect(cURLPrefixArgsUseTLS(clus, key), expected) -} - type etcdProcessCluster struct { cfg *etcdProcessClusterConfig procs []*etcdProcess @@ -464,3 +370,11 @@ func (epc *etcdProcessCluster) proxies() []*etcdProcess { func (epc *etcdProcessCluster) backends() []*etcdProcess { return epc.procs[:epc.cfg.clusterSize] } + +func (epc *etcdProcessCluster) endpoints() []string { + eps := make([]string, epc.cfg.clusterSize) + for i, ep := range epc.backends() { + eps[i] = ep.cfg.acurl + } + return eps +} diff --git a/e2e/v2_curl_test.go b/e2e/v2_curl_test.go new file mode 100644 index 000000000..d804e1ceb --- /dev/null +++ b/e2e/v2_curl_test.go @@ -0,0 +1,113 @@ +// 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 e2e + +import ( + "math/rand" + "testing" + + "github.com/coreos/etcd/pkg/testutil" +) + +func TestV2CurlNoTLS(t *testing.T) { testCurlPutGet(t, &configNoTLS) } +func TestV2CurlAutoTLS(t *testing.T) { testCurlPutGet(t, &configAutoTLS) } +func TestV2CurlAllTLS(t *testing.T) { testCurlPutGet(t, &configTLS) } +func TestV2CurlPeerTLS(t *testing.T) { testCurlPutGet(t, &configPeerTLS) } +func TestV2CurlClientTLS(t *testing.T) { testCurlPutGet(t, &configClientTLS) } +func TestV2CurlProxyNoTLS(t *testing.T) { testCurlPutGet(t, &configWithProxy) } +func TestV2CurlProxyTLS(t *testing.T) { testCurlPutGet(t, &configWithProxyTLS) } +func TestV2CurlProxyPeerTLS(t *testing.T) { testCurlPutGet(t, &configWithProxyPeerTLS) } +func TestV2CurlClientBoth(t *testing.T) { testCurlPutGet(t, &configClientBoth) } + +func testCurlPutGet(t *testing.T, cfg *etcdProcessClusterConfig) { + defer testutil.AfterTest(t) + + // test doesn't use quorum gets, so ensure there are no followers to avoid + // stale reads that will break the test + cfg = configStandalone(*cfg) + + epc, err := newEtcdProcessCluster(cfg) + if err != nil { + t.Fatalf("could not start etcd process cluster (%v)", err) + } + defer func() { + if err := epc.Close(); err != nil { + t.Fatalf("error closing etcd processes (%v)", err) + } + }() + + expectPut := `{"action":"set","node":{"key":"/testKey","value":"foo","` + expectGet := `{"action":"get","node":{"key":"/testKey","value":"foo","` + + if cfg.clientTLS == clientTLSAndNonTLS { + if err := cURLPut(epc, "testKey", "foo", expectPut); err != nil { + t.Fatalf("failed put with curl (%v)", err) + } + + if err := cURLGet(epc, "testKey", expectGet); err != nil { + t.Fatalf("failed get with curl (%v)", err) + } + if err := cURLGetUseTLS(epc, "testKey", expectGet); err != nil { + t.Fatalf("failed get with curl (%v)", err) + } + } else { + if err := cURLPut(epc, "testKey", "foo", expectPut); err != nil { + t.Fatalf("failed put with curl (%v)", err) + } + + if err := cURLGet(epc, "testKey", expectGet); err != nil { + t.Fatalf("failed get with curl (%v)", err) + } + } +} + +// cURLPrefixArgs builds the beginning of a curl command for a given key +// addressed to a random URL in the given cluster. +func cURLPrefixArgs(clus *etcdProcessCluster, key string) []string { + cmdArgs := []string{"curl"} + acurl := clus.procs[rand.Intn(clus.cfg.clusterSize)].cfg.acurl + + if clus.cfg.clientTLS == clientTLS { + cmdArgs = append(cmdArgs, "--cacert", caPath, "--cert", certPath, "--key", privateKeyPath) + } + keyURL := acurl + "/v2/keys/testKey" + cmdArgs = append(cmdArgs, "-L", keyURL) + return cmdArgs +} + +func cURLPrefixArgsUseTLS(clus *etcdProcessCluster, key string) []string { + cmdArgs := []string{"curl"} + if clus.cfg.clientTLS != clientTLSAndNonTLS { + panic("should not use cURLPrefixArgsUseTLS when serving only TLS or non-TLS") + } + cmdArgs = append(cmdArgs, "--cacert", caPath, "--cert", certPath, "--key", privateKeyPath) + acurl := clus.procs[rand.Intn(clus.cfg.clusterSize)].cfg.acurltls + keyURL := acurl + "/v2/keys/testKey" + cmdArgs = append(cmdArgs, "-L", keyURL) + return cmdArgs +} + +func cURLPut(clus *etcdProcessCluster, key, val, expected string) error { + args := append(cURLPrefixArgs(clus, key), "-XPUT", "-d", "value="+val) + return spawnWithExpect(args, expected) +} + +func cURLGet(clus *etcdProcessCluster, key, expected string) error { + return spawnWithExpect(cURLPrefixArgs(clus, key), expected) +} + +func cURLGetUseTLS(clus *etcdProcessCluster, key, expected string) error { + return spawnWithExpect(cURLPrefixArgsUseTLS(clus, key), expected) +}