1// Copyright 2016 The etcd Authors 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15package e2e 16 17import ( 18 "fmt" 19 "os" 20 "strings" 21 "testing" 22 "time" 23 24 "github.com/coreos/etcd/pkg/flags" 25 "github.com/coreos/etcd/pkg/testutil" 26 "github.com/coreos/etcd/version" 27) 28 29func TestCtlV3Version(t *testing.T) { testCtl(t, versionTest) } 30 31func versionTest(cx ctlCtx) { 32 if err := ctlV3Version(cx); err != nil { 33 cx.t.Fatalf("versionTest ctlV3Version error (%v)", err) 34 } 35} 36 37func ctlV3Version(cx ctlCtx) error { 38 cmdArgs := append(cx.PrefixArgs(), "version") 39 return spawnWithExpect(cmdArgs, version.Version) 40} 41 42// TestCtlV3DialWithHTTPScheme ensures that client handles endpoints with HTTPS scheme. 43func TestCtlV3DialWithHTTPScheme(t *testing.T) { 44 testCtl(t, dialWithSchemeTest, withCfg(configClientTLS)) 45} 46 47func dialWithSchemeTest(cx ctlCtx) { 48 cmdArgs := append(cx.prefixArgs(cx.epc.endpoints()), "put", "foo", "bar") 49 if err := spawnWithExpect(cmdArgs, "OK"); err != nil { 50 cx.t.Fatal(err) 51 } 52} 53 54type ctlCtx struct { 55 t *testing.T 56 cfg etcdProcessClusterConfig 57 quotaBackendBytes int64 58 noStrictReconfig bool 59 60 epc *etcdProcessCluster 61 62 envMap map[string]struct{} 63 64 dialTimeout time.Duration 65 66 quorum bool // if true, set up 3-node cluster and linearizable read 67 interactive bool 68 69 user string 70 pass string 71 72 // for compaction 73 compactPhysical bool 74} 75 76type ctlOption func(*ctlCtx) 77 78func (cx *ctlCtx) applyOpts(opts []ctlOption) { 79 for _, opt := range opts { 80 opt(cx) 81 } 82} 83 84func withCfg(cfg etcdProcessClusterConfig) ctlOption { 85 return func(cx *ctlCtx) { cx.cfg = cfg } 86} 87 88func withDialTimeout(timeout time.Duration) ctlOption { 89 return func(cx *ctlCtx) { cx.dialTimeout = timeout } 90} 91 92func withQuorum() ctlOption { 93 return func(cx *ctlCtx) { cx.quorum = true } 94} 95 96func withInteractive() ctlOption { 97 return func(cx *ctlCtx) { cx.interactive = true } 98} 99 100func withQuota(b int64) ctlOption { 101 return func(cx *ctlCtx) { cx.quotaBackendBytes = b } 102} 103 104func withCompactPhysical() ctlOption { 105 return func(cx *ctlCtx) { cx.compactPhysical = true } 106} 107 108func withNoStrictReconfig() ctlOption { 109 return func(cx *ctlCtx) { cx.noStrictReconfig = true } 110} 111 112func withFlagByEnv() ctlOption { 113 return func(cx *ctlCtx) { cx.envMap = make(map[string]struct{}) } 114} 115 116func testCtl(t *testing.T, testFunc func(ctlCtx), opts ...ctlOption) { 117 defer testutil.AfterTest(t) 118 119 ret := ctlCtx{ 120 t: t, 121 cfg: configAutoTLS, 122 dialTimeout: 7 * time.Second, 123 } 124 ret.applyOpts(opts) 125 126 os.Setenv("ETCDCTL_API", "3") 127 mustEtcdctl(t) 128 if !ret.quorum { 129 ret.cfg = *configStandalone(ret.cfg) 130 } 131 if ret.quotaBackendBytes > 0 { 132 ret.cfg.quotaBackendBytes = ret.quotaBackendBytes 133 } 134 ret.cfg.noStrictReconfig = ret.noStrictReconfig 135 136 epc, err := newEtcdProcessCluster(&ret.cfg) 137 if err != nil { 138 t.Fatalf("could not start etcd process cluster (%v)", err) 139 } 140 ret.epc = epc 141 142 defer func() { 143 os.Unsetenv("ETCDCTL_API") 144 if ret.envMap != nil { 145 for k := range ret.envMap { 146 os.Unsetenv(k) 147 } 148 } 149 if errC := ret.epc.Close(); errC != nil { 150 t.Fatalf("error closing etcd processes (%v)", errC) 151 } 152 }() 153 154 donec := make(chan struct{}) 155 go func() { 156 defer close(donec) 157 testFunc(ret) 158 }() 159 160 timeout := 2*ret.dialTimeout + time.Second 161 if ret.dialTimeout == 0 { 162 timeout = 30 * time.Second 163 } 164 select { 165 case <-time.After(timeout): 166 testutil.FatalStack(t, fmt.Sprintf("test timed out after %v", timeout)) 167 case <-donec: 168 } 169} 170 171func (cx *ctlCtx) prefixArgs(eps []string) []string { 172 if len(cx.epc.proxies()) > 0 { // TODO: add proxy check as in v2 173 panic("v3 proxy not implemented") 174 } 175 176 fmap := make(map[string]string) 177 fmap["endpoints"] = strings.Join(eps, ",") 178 fmap["dial-timeout"] = cx.dialTimeout.String() 179 if cx.epc.cfg.clientTLS == clientTLS { 180 if cx.epc.cfg.isClientAutoTLS { 181 fmap["insecure-transport"] = "false" 182 fmap["insecure-skip-tls-verify"] = "true" 183 } else { 184 fmap["cacert"] = caPath 185 fmap["cert"] = certPath 186 fmap["key"] = privateKeyPath 187 } 188 } 189 if cx.user != "" { 190 fmap["user"] = cx.user + ":" + cx.pass 191 } 192 193 useEnv := cx.envMap != nil 194 195 cmdArgs := []string{ctlBinPath} 196 for k, v := range fmap { 197 if useEnv { 198 ek := flags.FlagToEnv("ETCDCTL", k) 199 os.Setenv(ek, v) 200 cx.envMap[ek] = struct{}{} 201 } else { 202 cmdArgs = append(cmdArgs, fmt.Sprintf("--%s=%s", k, v)) 203 } 204 } 205 return cmdArgs 206} 207 208// PrefixArgs prefixes etcdctl command. 209// Make sure to unset environment variables after tests. 210func (cx *ctlCtx) PrefixArgs() []string { 211 return cx.prefixArgs(cx.epc.grpcEndpoints()) 212} 213 214func isGRPCTimedout(err error) bool { 215 return strings.Contains(err.Error(), "grpc: timed out trying to connect") 216} 217 218func (cx *ctlCtx) memberToRemove() (ep string, memberID string, clusterID string) { 219 n1 := cx.cfg.clusterSize 220 if n1 < 2 { 221 cx.t.Fatalf("%d-node is too small to test 'member remove'", n1) 222 } 223 224 resp, err := getMemberList(*cx) 225 if err != nil { 226 cx.t.Fatal(err) 227 } 228 if n1 != len(resp.Members) { 229 cx.t.Fatalf("expected %d, got %d", n1, len(resp.Members)) 230 } 231 232 ep = resp.Members[0].ClientURLs[0] 233 clusterID = fmt.Sprintf("%x", resp.Header.ClusterId) 234 memberID = fmt.Sprintf("%x", resp.Members[1].ID) 235 236 return ep, memberID, clusterID 237} 238