| // 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 e2e |
| |
| import ( |
| "fmt" |
| "os" |
| "strings" |
| "testing" |
| "time" |
| |
| "github.com/coreos/etcd/pkg/flags" |
| "github.com/coreos/etcd/pkg/testutil" |
| "github.com/coreos/etcd/version" |
| ) |
| |
| func TestCtlV3Version(t *testing.T) { testCtl(t, versionTest) } |
| |
| func versionTest(cx ctlCtx) { |
| if err := ctlV3Version(cx); err != nil { |
| cx.t.Fatalf("versionTest ctlV3Version error (%v)", err) |
| } |
| } |
| |
| func ctlV3Version(cx ctlCtx) error { |
| cmdArgs := append(cx.PrefixArgs(), "version") |
| return spawnWithExpect(cmdArgs, version.Version) |
| } |
| |
| // TestCtlV3DialWithHTTPScheme ensures that client handles endpoints with HTTPS scheme. |
| func TestCtlV3DialWithHTTPScheme(t *testing.T) { |
| testCtl(t, dialWithSchemeTest, withCfg(configClientTLS)) |
| } |
| |
| func dialWithSchemeTest(cx ctlCtx) { |
| cmdArgs := append(cx.prefixArgs(cx.epc.EndpointsV3()), "put", "foo", "bar") |
| if err := spawnWithExpect(cmdArgs, "OK"); err != nil { |
| cx.t.Fatal(err) |
| } |
| } |
| |
| type ctlCtx struct { |
| t *testing.T |
| cfg etcdProcessClusterConfig |
| quotaBackendBytes int64 |
| corruptFunc func(string) error |
| noStrictReconfig bool |
| |
| epc *etcdProcessCluster |
| |
| envMap map[string]struct{} |
| |
| dialTimeout time.Duration |
| |
| quorum bool // if true, set up 3-node cluster and linearizable read |
| interactive bool |
| |
| user string |
| pass string |
| |
| initialCorruptCheck bool |
| |
| // for compaction |
| compactPhysical bool |
| } |
| |
| type ctlOption func(*ctlCtx) |
| |
| func (cx *ctlCtx) applyOpts(opts []ctlOption) { |
| for _, opt := range opts { |
| opt(cx) |
| } |
| cx.initialCorruptCheck = true |
| } |
| |
| func withCfg(cfg etcdProcessClusterConfig) ctlOption { |
| return func(cx *ctlCtx) { cx.cfg = cfg } |
| } |
| |
| func withDialTimeout(timeout time.Duration) ctlOption { |
| return func(cx *ctlCtx) { cx.dialTimeout = timeout } |
| } |
| |
| func withQuorum() ctlOption { |
| return func(cx *ctlCtx) { cx.quorum = true } |
| } |
| |
| func withInteractive() ctlOption { |
| return func(cx *ctlCtx) { cx.interactive = true } |
| } |
| |
| func withQuota(b int64) ctlOption { |
| return func(cx *ctlCtx) { cx.quotaBackendBytes = b } |
| } |
| |
| func withCompactPhysical() ctlOption { |
| return func(cx *ctlCtx) { cx.compactPhysical = true } |
| } |
| |
| func withInitialCorruptCheck() ctlOption { |
| return func(cx *ctlCtx) { cx.initialCorruptCheck = true } |
| } |
| |
| func withCorruptFunc(f func(string) error) ctlOption { |
| return func(cx *ctlCtx) { cx.corruptFunc = f } |
| } |
| |
| func withNoStrictReconfig() ctlOption { |
| return func(cx *ctlCtx) { cx.noStrictReconfig = true } |
| } |
| |
| func withFlagByEnv() ctlOption { |
| return func(cx *ctlCtx) { cx.envMap = make(map[string]struct{}) } |
| } |
| |
| func testCtl(t *testing.T, testFunc func(ctlCtx), opts ...ctlOption) { |
| defer testutil.AfterTest(t) |
| |
| ret := ctlCtx{ |
| t: t, |
| cfg: configAutoTLS, |
| dialTimeout: 7 * time.Second, |
| } |
| ret.applyOpts(opts) |
| |
| mustEtcdctl(t) |
| if !ret.quorum { |
| ret.cfg = *configStandalone(ret.cfg) |
| } |
| if ret.quotaBackendBytes > 0 { |
| ret.cfg.quotaBackendBytes = ret.quotaBackendBytes |
| } |
| ret.cfg.noStrictReconfig = ret.noStrictReconfig |
| if ret.initialCorruptCheck { |
| ret.cfg.initialCorruptCheck = ret.initialCorruptCheck |
| } |
| |
| epc, err := newEtcdProcessCluster(&ret.cfg) |
| if err != nil { |
| t.Fatalf("could not start etcd process cluster (%v)", err) |
| } |
| ret.epc = epc |
| |
| defer func() { |
| if ret.envMap != nil { |
| for k := range ret.envMap { |
| os.Unsetenv(k) |
| } |
| } |
| if errC := ret.epc.Close(); errC != nil { |
| t.Fatalf("error closing etcd processes (%v)", errC) |
| } |
| }() |
| |
| donec := make(chan struct{}) |
| go func() { |
| defer close(donec) |
| testFunc(ret) |
| }() |
| |
| timeout := 2*ret.dialTimeout + time.Second |
| if ret.dialTimeout == 0 { |
| timeout = 30 * time.Second |
| } |
| select { |
| case <-time.After(timeout): |
| testutil.FatalStack(t, fmt.Sprintf("test timed out after %v", timeout)) |
| case <-donec: |
| } |
| } |
| |
| func (cx *ctlCtx) prefixArgs(eps []string) []string { |
| fmap := make(map[string]string) |
| fmap["endpoints"] = strings.Join(eps, ",") |
| fmap["dial-timeout"] = cx.dialTimeout.String() |
| if cx.epc.cfg.clientTLS == clientTLS { |
| if cx.epc.cfg.isClientAutoTLS { |
| fmap["insecure-transport"] = "false" |
| fmap["insecure-skip-tls-verify"] = "true" |
| } else if cx.epc.cfg.isClientCRL { |
| fmap["cacert"] = caPath |
| fmap["cert"] = revokedCertPath |
| fmap["key"] = revokedPrivateKeyPath |
| } else { |
| fmap["cacert"] = caPath |
| fmap["cert"] = certPath |
| fmap["key"] = privateKeyPath |
| } |
| } |
| if cx.user != "" { |
| fmap["user"] = cx.user + ":" + cx.pass |
| } |
| |
| useEnv := cx.envMap != nil |
| |
| cmdArgs := []string{ctlBinPath + "3"} |
| for k, v := range fmap { |
| if useEnv { |
| ek := flags.FlagToEnv("ETCDCTL", k) |
| os.Setenv(ek, v) |
| cx.envMap[ek] = struct{}{} |
| } else { |
| cmdArgs = append(cmdArgs, fmt.Sprintf("--%s=%s", k, v)) |
| } |
| } |
| return cmdArgs |
| } |
| |
| // PrefixArgs prefixes etcdctl command. |
| // Make sure to unset environment variables after tests. |
| func (cx *ctlCtx) PrefixArgs() []string { |
| return cx.prefixArgs(cx.epc.EndpointsV3()) |
| } |
| |
| func isGRPCTimedout(err error) bool { |
| return strings.Contains(err.Error(), "grpc: timed out trying to connect") |
| } |
| |
| func (cx *ctlCtx) memberToRemove() (ep string, memberID string, clusterID string) { |
| n1 := cx.cfg.clusterSize |
| if n1 < 2 { |
| cx.t.Fatalf("%d-node is too small to test 'member remove'", n1) |
| } |
| |
| resp, err := getMemberList(*cx) |
| if err != nil { |
| cx.t.Fatal(err) |
| } |
| if n1 != len(resp.Members) { |
| cx.t.Fatalf("expected %d, got %d", n1, len(resp.Members)) |
| } |
| |
| ep = resp.Members[0].ClientURLs[0] |
| clusterID = fmt.Sprintf("%x", resp.Header.ClusterId) |
| memberID = fmt.Sprintf("%x", resp.Members[1].ID) |
| |
| return ep, memberID, clusterID |
| } |