go.etcd.io/etcd@v3.3.27+incompatible/tests/e2e/ctl_v3_test.go (about) 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 15 package e2e 16 17 import ( 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 29 func TestCtlV3Version(t *testing.T) { testCtl(t, versionTest) } 30 31 func versionTest(cx ctlCtx) { 32 if err := ctlV3Version(cx); err != nil { 33 cx.t.Fatalf("versionTest ctlV3Version error (%v)", err) 34 } 35 } 36 37 func 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. 43 func TestCtlV3DialWithHTTPScheme(t *testing.T) { 44 testCtl(t, dialWithSchemeTest, withCfg(configClientTLS)) 45 } 46 47 func dialWithSchemeTest(cx ctlCtx) { 48 cmdArgs := append(cx.prefixArgs(cx.epc.EndpointsV3()), "put", "foo", "bar") 49 if err := spawnWithExpect(cmdArgs, "OK"); err != nil { 50 cx.t.Fatal(err) 51 } 52 } 53 54 type ctlCtx struct { 55 t *testing.T 56 apiPrefix string 57 cfg etcdProcessClusterConfig 58 quotaBackendBytes int64 59 corruptFunc func(string) error 60 noStrictReconfig bool 61 62 epc *etcdProcessCluster 63 64 envMap map[string]struct{} 65 66 dialTimeout time.Duration 67 68 quorum bool // if true, set up 3-node cluster and linearizable read 69 interactive bool 70 71 user string 72 pass string 73 74 initialCorruptCheck bool 75 76 // for compaction 77 compactPhysical bool 78 } 79 80 type ctlOption func(*ctlCtx) 81 82 func (cx *ctlCtx) applyOpts(opts []ctlOption) { 83 for _, opt := range opts { 84 opt(cx) 85 } 86 cx.initialCorruptCheck = true 87 } 88 89 func withCfg(cfg etcdProcessClusterConfig) ctlOption { 90 return func(cx *ctlCtx) { cx.cfg = cfg } 91 } 92 93 func withDialTimeout(timeout time.Duration) ctlOption { 94 return func(cx *ctlCtx) { cx.dialTimeout = timeout } 95 } 96 97 func withQuorum() ctlOption { 98 return func(cx *ctlCtx) { cx.quorum = true } 99 } 100 101 func withInteractive() ctlOption { 102 return func(cx *ctlCtx) { cx.interactive = true } 103 } 104 105 func withQuota(b int64) ctlOption { 106 return func(cx *ctlCtx) { cx.quotaBackendBytes = b } 107 } 108 109 func withCompactPhysical() ctlOption { 110 return func(cx *ctlCtx) { cx.compactPhysical = true } 111 } 112 113 func withInitialCorruptCheck() ctlOption { 114 return func(cx *ctlCtx) { cx.initialCorruptCheck = true } 115 } 116 117 func withCorruptFunc(f func(string) error) ctlOption { 118 return func(cx *ctlCtx) { cx.corruptFunc = f } 119 } 120 121 func withNoStrictReconfig() ctlOption { 122 return func(cx *ctlCtx) { cx.noStrictReconfig = true } 123 } 124 125 func withApiPrefix(p string) ctlOption { 126 return func(cx *ctlCtx) { cx.apiPrefix = p } 127 } 128 129 func withFlagByEnv() ctlOption { 130 return func(cx *ctlCtx) { cx.envMap = make(map[string]struct{}) } 131 } 132 133 func testCtl(t *testing.T, testFunc func(ctlCtx), opts ...ctlOption) { 134 defer testutil.AfterTest(t) 135 136 ret := ctlCtx{ 137 t: t, 138 cfg: configAutoTLS, 139 dialTimeout: 7 * time.Second, 140 } 141 ret.applyOpts(opts) 142 143 mustEtcdctl(t) 144 if !ret.quorum { 145 ret.cfg = *configStandalone(ret.cfg) 146 } 147 if ret.quotaBackendBytes > 0 { 148 ret.cfg.quotaBackendBytes = ret.quotaBackendBytes 149 } 150 ret.cfg.noStrictReconfig = ret.noStrictReconfig 151 if ret.initialCorruptCheck { 152 ret.cfg.initialCorruptCheck = ret.initialCorruptCheck 153 } 154 155 epc, err := newEtcdProcessCluster(&ret.cfg) 156 if err != nil { 157 t.Fatalf("could not start etcd process cluster (%v)", err) 158 } 159 ret.epc = epc 160 161 defer func() { 162 if ret.envMap != nil { 163 for k := range ret.envMap { 164 os.Unsetenv(k) 165 } 166 } 167 if errC := ret.epc.Close(); errC != nil { 168 t.Fatalf("error closing etcd processes (%v)", errC) 169 } 170 }() 171 172 donec := make(chan struct{}) 173 go func() { 174 defer close(donec) 175 testFunc(ret) 176 }() 177 178 timeout := 2*ret.dialTimeout + time.Second 179 if ret.dialTimeout == 0 { 180 timeout = 30 * time.Second 181 } 182 select { 183 case <-time.After(timeout): 184 testutil.FatalStack(t, fmt.Sprintf("test timed out after %v", timeout)) 185 case <-donec: 186 } 187 } 188 189 func (cx *ctlCtx) prefixArgs(eps []string) []string { 190 fmap := make(map[string]string) 191 fmap["endpoints"] = strings.Join(eps, ",") 192 fmap["dial-timeout"] = cx.dialTimeout.String() 193 if cx.epc.cfg.clientTLS == clientTLS { 194 if cx.epc.cfg.isClientAutoTLS { 195 fmap["insecure-transport"] = "false" 196 fmap["insecure-skip-tls-verify"] = "true" 197 } else if cx.epc.cfg.isClientCRL { 198 fmap["cacert"] = caPath 199 fmap["cert"] = revokedCertPath 200 fmap["key"] = revokedPrivateKeyPath 201 } else { 202 fmap["cacert"] = caPath 203 fmap["cert"] = certPath 204 fmap["key"] = privateKeyPath 205 } 206 } 207 if cx.user != "" { 208 fmap["user"] = cx.user + ":" + cx.pass 209 } 210 211 useEnv := cx.envMap != nil 212 213 cmdArgs := []string{ctlBinPath + "3"} 214 for k, v := range fmap { 215 if useEnv { 216 ek := flags.FlagToEnv("ETCDCTL", k) 217 os.Setenv(ek, v) 218 cx.envMap[ek] = struct{}{} 219 } else { 220 cmdArgs = append(cmdArgs, fmt.Sprintf("--%s=%s", k, v)) 221 } 222 } 223 return cmdArgs 224 } 225 226 // PrefixArgs prefixes etcdctl command. 227 // Make sure to unset environment variables after tests. 228 func (cx *ctlCtx) PrefixArgs() []string { 229 return cx.prefixArgs(cx.epc.EndpointsV3()) 230 } 231 232 func isGRPCTimedout(err error) bool { 233 return strings.Contains(err.Error(), "grpc: timed out trying to connect") 234 } 235 236 func (cx *ctlCtx) memberToRemove() (ep string, memberID string, clusterID string) { 237 n1 := cx.cfg.clusterSize 238 if n1 < 2 { 239 cx.t.Fatalf("%d-node is too small to test 'member remove'", n1) 240 } 241 242 resp, err := getMemberList(*cx) 243 if err != nil { 244 cx.t.Fatal(err) 245 } 246 if n1 != len(resp.Members) { 247 cx.t.Fatalf("expected %d, got %d", n1, len(resp.Members)) 248 } 249 250 ep = resp.Members[0].ClientURLs[0] 251 clusterID = fmt.Sprintf("%x", resp.Header.ClusterId) 252 memberID = fmt.Sprintf("%x", resp.Members[1].ID) 253 254 return ep, memberID, clusterID 255 }