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 etcd-io#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. Did some clean-ups.
  • Loading branch information
gyuho committed Jan 31, 2016
1 parent cb7ebe8 commit db35eca
Show file tree
Hide file tree
Showing 3 changed files with 555 additions and 419 deletions.
307 changes: 9 additions & 298 deletions e2e/etcd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,78 +15,18 @@
package e2e

import (
"fmt"
"math/rand"
"net/url"
"os"
"strings"
"testing"

"github.com/coreos/etcd/Godeps/_workspace/src/github.com/coreos/gexpect"
"github.com/coreos/etcd/pkg/fileutil"
"github.com/coreos/etcd/pkg/testutil"
)

const (
etcdProcessBasePort = 20000
certPath = "../integration/fixtures/server.crt"
privateKeyPath = "../integration/fixtures/server.key.insecure"
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",
},
)
}

func TestBasicOpsClientTLS(t *testing.T) {
func TestCurlNoTLS(t *testing.T) { testCurlPutGet(t, &defaultConfig) }
func TestCurlAllTLS(t *testing.T) { testCurlPutGet(t, &defaultConfigTLS) }
func TestCurlPeerTLS(t *testing.T) { testCurlPutGet(t, &defaultConfigPeerTLS) }
func TestCurlClientTLS(t *testing.T) { testCurlPutGet(t, &defaultConfigClientTLS) }
func testCurlPutGet(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,242 +39,13 @@ 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)
}
}

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

type etcdProcess struct {
cfg *etcdProcessConfig
proc *gexpect.ExpectSubprocess
donec chan struct{} // closed when Interact() terminates
}

type etcdProcessConfig struct {
args []string
dataDirPath string
acurl url.URL
isProxy bool
}

type etcdProcessClusterConfig struct {
clusterSize int
proxySize int
isClientTLS bool
isPeerTLS bool
initialToken string
}

// newEtcdProcessCluster launches a new cluster from etcd processes, returning
// a new etcdProcessCluster once all nodes are ready to accept client requests.
func newEtcdProcessCluster(cfg *etcdProcessClusterConfig) (*etcdProcessCluster, error) {
etcdCfgs := cfg.etcdProcessConfigs()
epc := &etcdProcessCluster{
cfg: cfg,
procs: make([]*etcdProcess, cfg.clusterSize+cfg.proxySize),
}

// launch etcd processes
for i := range etcdCfgs {
proc, err := newEtcdProcess(etcdCfgs[i])
if err != nil {
epc.Close()
return nil, err
}
epc.procs[i] = proc
}

// wait for cluster to start
readyC := make(chan error, cfg.clusterSize+cfg.proxySize)
readyStr := "set the initial cluster version"
for i := range etcdCfgs {
go func(etcdp *etcdProcess) {
rs := readyStr
if etcdp.cfg.isProxy {
rs = "listening for client requests"
}
_, 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
close(etcdp.donec)
}(epc.procs[i])
}
for range etcdCfgs {
if err := <-readyC; err != nil {
epc.Close()
return nil, err
}
}
return epc, nil
}

func newEtcdProcess(cfg *etcdProcessConfig) (*etcdProcess, error) {
if fileutil.Exist("../bin/etcd") == false {
return nil, fmt.Errorf("could not find etcd binary")
}
if err := os.RemoveAll(cfg.dataDirPath); err != nil {
return nil, err
}
child, err := spawnCmd(append([]string{"../bin/etcd"}, cfg.args...))
if err != nil {
return nil, err
}
child.Capture()
return &etcdProcess{cfg: cfg, proc: child, donec: make(chan struct{})}, nil
}

func (cfg *etcdProcessClusterConfig) etcdProcessConfigs() []*etcdProcessConfig {
clientScheme := "http"
if cfg.isClientTLS {
clientScheme = "https"
}
peerScheme := "http"
if cfg.isPeerTLS {
peerScheme = "https"
}

etcdCfgs := make([]*etcdProcessConfig, cfg.clusterSize+cfg.proxySize)
initialCluster := make([]string, cfg.clusterSize)
for i := 0; i < cfg.clusterSize; i++ {
port := etcdProcessBasePort + 2*i
curl := url.URL{Scheme: clientScheme, Host: fmt.Sprintf("localhost:%d", port)}
purl := url.URL{Scheme: peerScheme, Host: fmt.Sprintf("localhost:%d", port+1)}
name := fmt.Sprintf("testname%d", i)
dataDirPath := name + ".etcd"
initialCluster[i] = fmt.Sprintf("%s=%s", name, purl.String())

args := []string{
"--name", name,
"--listen-client-urls", curl.String(),
"--advertise-client-urls", curl.String(),
"--listen-peer-urls", purl.String(),
"--initial-advertise-peer-urls", purl.String(),
"--initial-cluster-token", cfg.initialToken,
"--data-dir", dataDirPath,
}
if cfg.isClientTLS {
tlsClientArgs := []string{
"--cert-file", certPath,
"--key-file", privateKeyPath,
"--ca-file", caPath,
}
args = append(args, tlsClientArgs...)
}
if cfg.isPeerTLS {
tlsPeerArgs := []string{
"--peer-cert-file", certPath,
"--peer-key-file", privateKeyPath,
"--peer-ca-file", caPath,
}
args = append(args, tlsPeerArgs...)
}

etcdCfgs[i] = &etcdProcessConfig{
args: args,
dataDirPath: dataDirPath,
acurl: curl,
}
}
for i := 0; i < cfg.proxySize; i++ {
port := etcdProcessBasePort + 2*cfg.clusterSize + i + 1
curl := url.URL{Scheme: clientScheme, Host: fmt.Sprintf("localhost:%d", port)}
name := fmt.Sprintf("testname-proxy%d", i)
dataDirPath := name + ".etcd"
args := []string{
"--name", name,
"--proxy", "on",
"--listen-client-urls", curl.String(),
"--data-dir", dataDirPath,
}
etcdCfgs[cfg.clusterSize+i] = &etcdProcessConfig{
args: args,
dataDirPath: dataDirPath,
acurl: curl,
isProxy: true,
}
}

initialClusterArgs := []string{"--initial-cluster", strings.Join(initialCluster, ",")}
for i := range etcdCfgs {
etcdCfgs[i].args = append(etcdCfgs[i].args, initialClusterArgs...)
}

return etcdCfgs
}

func (epc *etcdProcessCluster) Close() (err error) {
for _, p := range epc.procs {
if p == nil {
continue
}
os.RemoveAll(p.cfg.dataDirPath)
if curErr := p.proc.Close(); curErr != nil {
if err != nil {
err = fmt.Errorf("%v; %v", err, curErr)
} else {
err = curErr
}
}
<-p.donec
}
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)
}

func spawnWithExpect(args []string, expected string) error {
proc, err := spawnCmd(args)
if err != nil {
return err
}
if _, err := proc.ExpectRegex(expected); err != nil {
return err
t.Logf("failed get with curl (%v)", err)
return
}
return proc.Close()
}
Loading

0 comments on commit db35eca

Please sign in to comment.