github.com/lfch/etcd-io/tests/v3@v3.0.0-20221004140520-eac99acd3e9d/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 "context" 19 "fmt" 20 "os" 21 "strings" 22 "testing" 23 "time" 24 25 "github.com/stretchr/testify/assert" 26 "github.com/lfch/etcd-io/api/v3/version" 27 "github.com/lfch/etcd-io/client/pkg/v3/fileutil" 28 "github.com/lfch/etcd-io/client/pkg/v3/testutil" 29 "github.com/lfch/etcd-io/pkg/v3/flags" 30 "github.com/lfch/etcd-io/tests/v3/framework/e2e" 31 ) 32 33 func TestCtlV3Version(t *testing.T) { testCtl(t, versionTest) } 34 35 func TestClusterVersion(t *testing.T) { 36 e2e.BeforeTest(t) 37 38 tests := []struct { 39 name string 40 rollingStart bool 41 }{ 42 { 43 name: "When start servers at the same time", 44 rollingStart: false, 45 }, 46 { 47 name: "When start servers one by one", 48 rollingStart: true, 49 }, 50 } 51 52 for _, tt := range tests { 53 t.Run(tt.name, func(t *testing.T) { 54 binary := e2e.BinDir + "/etcd" 55 if !fileutil.Exist(binary) { 56 t.Skipf("%q does not exist", binary) 57 } 58 e2e.BeforeTest(t) 59 cfg := e2e.NewConfigNoTLS() 60 cfg.ExecPath = binary 61 cfg.SnapshotCount = 3 62 cfg.BaseScheme = "unix" // to avoid port conflict 63 cfg.RollingStart = tt.rollingStart 64 65 epc, err := e2e.NewEtcdProcessCluster(context.TODO(), t, cfg) 66 if err != nil { 67 t.Fatalf("could not start etcd process cluster (%v)", err) 68 } 69 defer func() { 70 if errC := epc.Close(); errC != nil { 71 t.Fatalf("error closing etcd processes (%v)", errC) 72 } 73 }() 74 75 ctx := ctlCtx{ 76 t: t, 77 cfg: *cfg, 78 epc: epc, 79 } 80 cv := version.Cluster(version.Version) 81 clusterVersionTest(ctx, `"etcdcluster":"`+cv) 82 }) 83 } 84 } 85 86 func versionTest(cx ctlCtx) { 87 if err := ctlV3Version(cx); err != nil { 88 cx.t.Fatalf("versionTest ctlV3Version error (%v)", err) 89 } 90 } 91 92 func clusterVersionTest(cx ctlCtx, expected string) { 93 var err error 94 for i := 0; i < 35; i++ { 95 if err = e2e.CURLGet(cx.epc, e2e.CURLReq{Endpoint: "/version", Expected: expected}); err != nil { 96 cx.t.Logf("#%d: v3 is not ready yet (%v)", i, err) 97 time.Sleep(200 * time.Millisecond) 98 continue 99 } 100 break 101 } 102 if err != nil { 103 cx.t.Fatalf("failed cluster version test expected %v got (%v)", expected, err) 104 } 105 } 106 107 func ctlV3Version(cx ctlCtx) error { 108 cmdArgs := append(cx.PrefixArgs(), "version") 109 return e2e.SpawnWithExpectWithEnv(cmdArgs, cx.envMap, version.Version) 110 } 111 112 // TestCtlV3DialWithHTTPScheme ensures that client handles Endpoints with HTTPS scheme. 113 func TestCtlV3DialWithHTTPScheme(t *testing.T) { 114 testCtl(t, dialWithSchemeTest, withCfg(*e2e.NewConfigClientTLS())) 115 } 116 117 func dialWithSchemeTest(cx ctlCtx) { 118 cmdArgs := append(cx.prefixArgs(cx.epc.EndpointsV3()), "put", "foo", "bar") 119 if err := e2e.SpawnWithExpectWithEnv(cmdArgs, cx.envMap, "OK"); err != nil { 120 cx.t.Fatal(err) 121 } 122 } 123 124 type ctlCtx struct { 125 t *testing.T 126 apiPrefix string 127 cfg e2e.EtcdProcessClusterConfig 128 129 corruptFunc func(string) error 130 disableStrictReconfigCheck bool 131 132 epc *e2e.EtcdProcessCluster 133 134 envMap map[string]string 135 136 dialTimeout time.Duration 137 testTimeout time.Duration 138 139 quorum bool // if true, set up 3-node cluster and linearizable read 140 interactive bool 141 142 user string 143 pass string 144 145 initialCorruptCheck bool 146 147 // dir that was used during the test 148 dataDir string 149 } 150 151 type ctlOption func(*ctlCtx) 152 153 func (cx *ctlCtx) applyOpts(opts []ctlOption) { 154 for _, opt := range opts { 155 opt(cx) 156 } 157 158 cx.initialCorruptCheck = true 159 } 160 161 func withCfg(cfg e2e.EtcdProcessClusterConfig) ctlOption { 162 return func(cx *ctlCtx) { cx.cfg = cfg } 163 } 164 165 func withDialTimeout(timeout time.Duration) ctlOption { 166 return func(cx *ctlCtx) { cx.dialTimeout = timeout } 167 } 168 169 func withTestTimeout(timeout time.Duration) ctlOption { 170 return func(cx *ctlCtx) { cx.testTimeout = timeout } 171 } 172 173 func withQuorum() ctlOption { 174 return func(cx *ctlCtx) { cx.quorum = true } 175 } 176 177 func withInteractive() ctlOption { 178 return func(cx *ctlCtx) { cx.interactive = true } 179 } 180 181 func withInitialCorruptCheck() ctlOption { 182 return func(cx *ctlCtx) { cx.initialCorruptCheck = true } 183 } 184 185 func withCorruptFunc(f func(string) error) ctlOption { 186 return func(cx *ctlCtx) { cx.corruptFunc = f } 187 } 188 189 func withDisableStrictReconfig() ctlOption { 190 return func(cx *ctlCtx) { cx.disableStrictReconfigCheck = true } 191 } 192 193 func withApiPrefix(p string) ctlOption { 194 return func(cx *ctlCtx) { cx.apiPrefix = p } 195 } 196 197 func withFlagByEnv() ctlOption { 198 return func(cx *ctlCtx) { cx.envMap = make(map[string]string) } 199 } 200 201 // This function must be called after the `withCfg`, otherwise its value 202 // may be overwritten by `withCfg`. 203 func withMaxConcurrentStreams(streams uint32) ctlOption { 204 return func(cx *ctlCtx) { 205 cx.cfg.MaxConcurrentStreams = streams 206 } 207 } 208 209 func testCtl(t *testing.T, testFunc func(ctlCtx), opts ...ctlOption) { 210 testCtlWithOffline(t, testFunc, nil, opts...) 211 } 212 213 func getDefaultCtlCtx(t *testing.T) ctlCtx { 214 return ctlCtx{ 215 t: t, 216 cfg: *e2e.NewConfigAutoTLS(), 217 dialTimeout: 7 * time.Second, 218 } 219 } 220 221 func testCtlWithOffline(t *testing.T, testFunc func(ctlCtx), testOfflineFunc func(ctlCtx), opts ...ctlOption) { 222 e2e.BeforeTest(t) 223 224 ret := getDefaultCtlCtx(t) 225 ret.applyOpts(opts) 226 227 if !ret.quorum { 228 ret.cfg = *e2e.ConfigStandalone(ret.cfg) 229 } 230 ret.cfg.DisableStrictReconfigCheck = ret.disableStrictReconfigCheck 231 if ret.initialCorruptCheck { 232 ret.cfg.InitialCorruptCheck = ret.initialCorruptCheck 233 } 234 if testOfflineFunc != nil { 235 ret.cfg.KeepDataDir = true 236 } 237 238 epc, err := e2e.NewEtcdProcessCluster(context.TODO(), t, &ret.cfg) 239 if err != nil { 240 t.Fatalf("could not start etcd process cluster (%v)", err) 241 } 242 ret.epc = epc 243 ret.dataDir = epc.Procs[0].Config().DataDirPath 244 245 runCtlTest(t, testFunc, testOfflineFunc, ret) 246 } 247 248 func runCtlTest(t *testing.T, testFunc func(ctlCtx), testOfflineFunc func(ctlCtx), cx ctlCtx) { 249 defer func() { 250 if cx.envMap != nil { 251 for k := range cx.envMap { 252 os.Unsetenv(k) 253 } 254 cx.envMap = make(map[string]string) 255 } 256 if cx.epc != nil { 257 if errC := cx.epc.Close(); errC != nil { 258 t.Fatalf("error closing etcd processes (%v)", errC) 259 } 260 } 261 }() 262 263 donec := make(chan struct{}) 264 go func() { 265 defer close(donec) 266 testFunc(cx) 267 t.Log("---testFunc logic DONE") 268 }() 269 270 timeout := cx.getTestTimeout() 271 272 select { 273 case <-time.After(timeout): 274 testutil.FatalStack(t, fmt.Sprintf("test timed out after %v", timeout)) 275 case <-donec: 276 } 277 278 t.Log("closing test cluster...") 279 assert.NoError(t, cx.epc.Close()) 280 cx.epc = nil 281 t.Log("closed test cluster...") 282 283 if testOfflineFunc != nil { 284 testOfflineFunc(cx) 285 } 286 } 287 288 func (cx *ctlCtx) getTestTimeout() time.Duration { 289 timeout := cx.testTimeout 290 if timeout == 0 { 291 timeout = 2*cx.dialTimeout + time.Second 292 if cx.dialTimeout == 0 { 293 timeout = 30 * time.Second 294 } 295 } 296 return timeout 297 } 298 299 func (cx *ctlCtx) prefixArgs(eps []string) []string { 300 fmap := make(map[string]string) 301 fmap["endpoints"] = strings.Join(eps, ",") 302 fmap["dial-timeout"] = cx.dialTimeout.String() 303 if cx.epc.Cfg.ClientTLS == e2e.ClientTLS { 304 if cx.epc.Cfg.IsClientAutoTLS { 305 fmap["insecure-transport"] = "false" 306 fmap["insecure-skip-tls-verify"] = "true" 307 } else if cx.epc.Cfg.IsClientCRL { 308 fmap["cacert"] = e2e.CaPath 309 fmap["cert"] = e2e.RevokedCertPath 310 fmap["key"] = e2e.RevokedPrivateKeyPath 311 } else { 312 fmap["cacert"] = e2e.CaPath 313 fmap["cert"] = e2e.CertPath 314 fmap["key"] = e2e.PrivateKeyPath 315 } 316 } 317 if cx.user != "" { 318 fmap["user"] = cx.user + ":" + cx.pass 319 } 320 321 useEnv := cx.envMap != nil 322 323 cmdArgs := []string{e2e.CtlBinPath} 324 for k, v := range fmap { 325 if useEnv { 326 ek := flags.FlagToEnv("ETCDCTL", k) 327 cx.envMap[ek] = v 328 } else { 329 cmdArgs = append(cmdArgs, fmt.Sprintf("--%s=%s", k, v)) 330 } 331 } 332 return cmdArgs 333 } 334 335 // PrefixArgs prefixes etcdctl command. 336 // Make sure to unset environment variables after tests. 337 func (cx *ctlCtx) PrefixArgs() []string { 338 return cx.prefixArgs(cx.epc.EndpointsV3()) 339 } 340 341 // PrefixArgsUtl returns prefix of the command that is etcdutl 342 // Please not thet 'utl' compatible commands does not consume --endpoints flag. 343 func (cx *ctlCtx) PrefixArgsUtl() []string { 344 return []string{e2e.UtlBinPath} 345 } 346 347 func isGRPCTimedout(err error) bool { 348 return strings.Contains(err.Error(), "grpc: timed out trying to connect") 349 } 350 351 func (cx *ctlCtx) memberToRemove() (ep string, memberID string, clusterID string) { 352 n1 := cx.cfg.ClusterSize 353 if n1 < 2 { 354 cx.t.Fatalf("%d-node is too small to test 'member remove'", n1) 355 } 356 357 resp, err := getMemberList(*cx) 358 if err != nil { 359 cx.t.Fatal(err) 360 } 361 if n1 != len(resp.Members) { 362 cx.t.Fatalf("expected %d, got %d", n1, len(resp.Members)) 363 } 364 365 ep = resp.Members[0].ClientURLs[0] 366 clusterID = fmt.Sprintf("%x", resp.Header.ClusterId) 367 memberID = fmt.Sprintf("%x", resp.Members[1].ID) 368 369 return ep, memberID, clusterID 370 }