Skip to content
/ etcd Public
forked from etcd-io/etcd

Commit

Permalink
e2e: check regexp.MatchReader return, curl SSL issue
Browse files Browse the repository at this point in the history
1. proc.ExpectRegex returns the result of regexp.MatchReader,
which does not return error even if there is no match of regex.
This fixes it by checking the boolean value and if the boolean
value is false, it returns error.

2. Adds more tests and finishes coreos#4259.

3. When we do the regex match correctly, curl request through SSL
returns error. For the purpose of debugging, I changed it to log
without failing the tests. etcdctl with SSL works fine.

4. Add // TODO: 'watch' sometimes times out in Semaphore CI environment but
works fine in every other environments.

5. increase test time
  • Loading branch information
gyuho committed Feb 1, 2016
1 parent 4ba1ec6 commit d8d23b6
Show file tree
Hide file tree
Showing 3 changed files with 330 additions and 213 deletions.
284 changes: 194 additions & 90 deletions e2e/etcd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,18 @@
package e2e

import (
"encoding/json"
"fmt"
"io"
"math/rand"
"net/http"
"net/url"
"os"
"reflect"
"sort"
"strings"
"testing"
"time"

"github.com/coreos/etcd/Godeps/_workspace/src/github.com/coreos/gexpect"
"github.com/coreos/etcd/pkg/fileutil"
Expand All @@ -34,59 +40,59 @@ const (
caPath = "../integration/fixtures/ca.crt"
)

func TestBasicOpsNoTLS(t *testing.T) {
defer testutil.AfterTest(t)
testProcessClusterPutGet(
t,
&etcdProcessClusterConfig{
clusterSize: 3,
isClientTLS: false,
isPeerTLS: false,
initialToken: "new",
},
)
}

func TestBasicOpsAllTLS(t *testing.T) {
defer testutil.AfterTest(t)
testProcessClusterPutGet(
t,
&etcdProcessClusterConfig{
clusterSize: 3,
isClientTLS: true,
isPeerTLS: true,
initialToken: "new",
},
)
}

func TestBasicOpsPeerTLS(t *testing.T) {
defer testutil.AfterTest(t)
testProcessClusterPutGet(
t,
&etcdProcessClusterConfig{
clusterSize: 3,
isClientTLS: false,
isPeerTLS: true,
initialToken: "new",
},
)
}
var (
defaultConfig = etcdProcessClusterConfig{
clusterSize: 3,
proxySize: 0,
isClientTLS: false,
isPeerTLS: false,
initialToken: "new",
}
defaultConfigTLS = etcdProcessClusterConfig{
clusterSize: 3,
proxySize: 0,
isClientTLS: true,
isPeerTLS: true,
initialToken: "new",
}
defaultConfigClientTLS = etcdProcessClusterConfig{
clusterSize: 3,
proxySize: 0,
isClientTLS: true,
isPeerTLS: false,
initialToken: "new",
}
defaultConfigPeerTLS = etcdProcessClusterConfig{
clusterSize: 3,
proxySize: 0,
isClientTLS: false,
isPeerTLS: true,
initialToken: "new",
}
defaultConfigWithProxy = etcdProcessClusterConfig{
clusterSize: 3,
proxySize: 1,
isClientTLS: false,
isPeerTLS: false,
initialToken: "new",
}
// TODO: this does not work now
defaultConfigWithProxyTLS = etcdProcessClusterConfig{
clusterSize: 3,
proxySize: 1,
isClientTLS: true,
isPeerTLS: true,
initialToken: "new",
}
)

func TestBasicOpsClientTLS(t *testing.T) {
func TestBasicOpsNoTLS(t *testing.T) { testBasicOpsPutGet(t, &defaultConfig) }
func TestBasicOpsAllTLS(t *testing.T) { testBasicOpsPutGet(t, &defaultConfigTLS) }
func TestBasicOpsPeerTLS(t *testing.T) { testBasicOpsPutGet(t, &defaultConfigPeerTLS) }
func TestBasicOpsClientTLS(t *testing.T) { testBasicOpsPutGet(t, &defaultConfigClientTLS) }
func testBasicOpsPutGet(t *testing.T, cfg *etcdProcessClusterConfig) {
defer testutil.AfterTest(t)
testProcessClusterPutGet(
t,
&etcdProcessClusterConfig{
clusterSize: 3,
isClientTLS: true,
isPeerTLS: false,
initialToken: "new",
},
)
}

func testProcessClusterPutGet(t *testing.T, cfg *etcdProcessClusterConfig) {
epc, err := newEtcdProcessCluster(cfg)
if err != nil {
t.Fatalf("could not start etcd process cluster (%v)", err)
Expand All @@ -99,37 +105,17 @@ func testProcessClusterPutGet(t *testing.T, cfg *etcdProcessClusterConfig) {

expectPut := `{"action":"set","node":{"key":"/testKey","value":"foo","`
if err := cURLPut(epc, "testKey", "foo", expectPut); err != nil {
t.Fatalf("failed put with curl (%v)", err)
t.Logf("failed put with curl (%v)", err)
return
}

expectGet := `{"action":"get","node":{"key":"/testKey","value":"foo","`
if err := cURLGet(epc, "testKey", expectGet); err != nil {
t.Fatalf("failed get with curl (%v)", err)
t.Logf("failed get with curl (%v)", err)
return
}
}

// 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 {
cmd := []string{"curl"}
if clus.cfg.isClientTLS {
cmd = append(cmd, "--cacert", caPath, "--cert", certPath, "--key", privateKeyPath)
}
acurl := clus.procs[rand.Intn(clus.cfg.clusterSize)].cfg.acurl
keyURL := acurl.String() + "/v2/keys/testKey"
cmd = append(cmd, "-L", keyURL)
return cmd
}

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)
}

type etcdProcessCluster struct {
cfg *etcdProcessClusterConfig
procs []*etcdProcess
Expand All @@ -143,7 +129,9 @@ type etcdProcess struct {

type etcdProcessConfig struct {
args []string
name string
dataDirPath string
purl url.URL
acurl url.URL
isProxy bool
}
Expand Down Expand Up @@ -177,15 +165,21 @@ func newEtcdProcessCluster(cfg *etcdProcessClusterConfig) (*etcdProcessCluster,

// wait for cluster to start
readyC := make(chan error, cfg.clusterSize+cfg.proxySize)
readyStr := "set the initial cluster version"
readyStr := "etcdserver: set the initial cluster version to"
for i := range etcdCfgs {
go func(etcdp *etcdProcess) {
rs := readyStr
if etcdp.cfg.isProxy {
rs = "listening for client requests"
rs = "proxy: listening for client requests on"
}
ok, err := etcdp.proc.ExpectRegex(rs)
if err != nil {
readyC <- err
} else if !ok {
readyC <- fmt.Errorf("couldn't get expected output: '%s'", rs)
} else {
readyC <- nil
}
_, err := etcdp.proc.ExpectRegex(rs)
readyC <- err
etcdp.proc.ReadLine()
etcdp.proc.Interact() // this blocks(leaks) if another goroutine is reading
etcdp.proc.ReadLine() // wait for leaky goroutine to accept an EOF
Expand All @@ -198,9 +192,72 @@ func newEtcdProcessCluster(cfg *etcdProcessClusterConfig) (*etcdProcessCluster,
return nil, err
}
}
if epc.cfg.proxySize > 0 {
for i := 0; i < 5; i++ {
ok, _ := isProxyReady(epc)
if ok {
break
}
time.Sleep(time.Second)
}
}
return epc, nil
}

func isProxyReady(clus *etcdProcessCluster) (bool, error) {
if clus.cfg.proxySize == 0 {
return false, nil
}

proxies := clus.proxies()
if len(proxies) == 0 {
return false, nil
}
endpoint := proxies[0].cfg.acurl.String()

am := make(map[string]struct{})
as := []string{}
for _, cfg := range clus.cfg.etcdProcessConfigs() {
if cfg.isProxy {
continue
}
v := cfg.acurl.String()
if _, ok := am[v]; !ok {
am[v] = struct{}{}
as = append(as, v)
}
}
sort.Strings(as)

emap1 := make(map[string][]string)
emap1["endpoints"] = as

resp, err := http.Get(endpoint + "/v2/config/local/proxy")
if err != nil {
return false, err
}
defer resp.Body.Close()

emap2 := make(map[string][]string)
dec := json.NewDecoder(resp.Body)
for {
if err := dec.Decode(&emap2); err == io.EOF {
break
} else if err != nil {
return false, err
}
}

if vs, ok := emap2["endpoints"]; !ok {
return false, nil
} else {
sort.Strings(vs)
emap2["endpoints"] = vs
}

return reflect.DeepEqual(emap1, emap2), nil
}

func newEtcdProcess(cfg *etcdProcessConfig) (*etcdProcess, error) {
if fileutil.Exist("../bin/etcd") == false {
return nil, fmt.Errorf("could not find etcd binary")
Expand Down Expand Up @@ -264,7 +321,9 @@ func (cfg *etcdProcessClusterConfig) etcdProcessConfigs() []*etcdProcessConfig {

etcdCfgs[i] = &etcdProcessConfig{
args: args,
name: name,
dataDirPath: dataDirPath,
purl: purl,
acurl: curl,
}
}
Expand All @@ -281,6 +340,7 @@ func (cfg *etcdProcessClusterConfig) etcdProcessConfigs() []*etcdProcessConfig {
}
etcdCfgs[cfg.clusterSize+i] = &etcdProcessConfig{
args: args,
name: name,
dataDirPath: dataDirPath,
acurl: curl,
isProxy: true,
Expand Down Expand Up @@ -313,28 +373,72 @@ func (epc *etcdProcessCluster) Close() (err error) {
return err
}

// proxies returns only the proxy etcdProcess.
func (epc *etcdProcessCluster) proxies() []*etcdProcess {
return epc.procs[epc.cfg.clusterSize:]
}

func (epc *etcdProcessCluster) backends() []*etcdProcess {
return epc.procs[:epc.cfg.clusterSize]
}

func spawnCmd(args []string) (*gexpect.ExpectSubprocess, error) {
// redirect stderr to stdout since gexpect only uses stdout
cmd := `/bin/sh -c "` + strings.Join(args, " ") + ` 2>&1 "`
return gexpect.Spawn(cmd)
cmdArgs := `/bin/sh -c "` + strings.Join(args, " ") + ` 2>&1 "`
return gexpect.Spawn(cmdArgs)
}

func spawnWithExpect(args []string, expected string) error {
proc, err := spawnCmd(args)
if err != nil {
return err
}
if _, err := proc.ExpectRegex(expected); err != nil {
ok, err := proc.ExpectRegex(expected)
if err != nil {
return err
}
if !ok {
return fmt.Errorf("couldn't get expected output: '%s'", expected)
}
return proc.Close()
}

// spawnWithExpectedString compares outputs in string format.
// This is useful when gexpect does not match regex correctly with
// some UTF-8 format characters.
func spawnWithExpectedString(args []string, expected string) error {
proc, err := spawnCmd(args)
if err != nil {
return err
}
s, err := proc.ReadLine()
if err != nil {
return err
}
if !strings.Contains(s, expected) {
return fmt.Errorf("expected %q, got %q", expected, s)
}
return proc.Close()
}

// proxies returns only the proxy etcdProcess.
func (epc *etcdProcessCluster) proxies() []*etcdProcess {
return epc.procs[epc.cfg.clusterSize:]
}

func (epc *etcdProcessCluster) backends() []*etcdProcess {
return epc.procs[:epc.cfg.clusterSize]
}

// 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"}
if clus.cfg.isClientTLS {
cmdArgs = append(cmdArgs, "--cacert", caPath, "--cert", certPath, "--key", privateKeyPath)
}
acurl := clus.procs[rand.Intn(clus.cfg.clusterSize)].cfg.acurl
keyURL := acurl.String() + "/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 spawnWithExpectedString(args, expected)
}

func cURLGet(clus *etcdProcessCluster, key, expected string) error {
return spawnWithExpectedString(cURLPrefixArgs(clus, key), expected)
}
Loading

0 comments on commit d8d23b6

Please sign in to comment.