mirror of
https://github.com/gluster/glusterd2.git
synced 2026-02-05 12:45:38 +01:00
255 lines
5.5 KiB
Go
255 lines
5.5 KiB
Go
package e2e
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"os"
|
|
"os/exec"
|
|
"path"
|
|
"path/filepath"
|
|
"syscall"
|
|
"testing"
|
|
"time"
|
|
|
|
toml "github.com/pelletier/go-toml"
|
|
)
|
|
|
|
type testProcess struct {
|
|
Cmd *exec.Cmd
|
|
}
|
|
|
|
// IsRunning will return true if the process is currently
|
|
// running.
|
|
func (tp *testProcess) IsRunning() bool {
|
|
process, err := os.FindProcess(tp.Cmd.Process.Pid)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
|
|
if err := process.Signal(syscall.Signal(0)); err != nil {
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// https://golang.org/src/os/exec_unix.go
|
|
var errFinished = errors.New("os: process already finished")
|
|
|
|
// Stop will terminate the associated process. It will attempt a graceful
|
|
// shutdown before killing the process.
|
|
func (tp *testProcess) Stop() error {
|
|
tp.Cmd.Process.Signal(os.Interrupt) // try shutting down gracefully
|
|
time.Sleep(2 * time.Second)
|
|
if tp.IsRunning() {
|
|
time.Sleep(2 * time.Second)
|
|
} else {
|
|
return nil
|
|
}
|
|
if err := tp.Cmd.Process.Kill(); err != nil {
|
|
if err.Error() != errFinished.Error() {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type gdProcess struct {
|
|
testProcess
|
|
ClientAddress string `toml:"clientaddress"`
|
|
PeerAddress string `toml:"peeraddress"`
|
|
LocalStateDir string `toml:"localstatedir"`
|
|
RestAuth bool `toml:"restauth"`
|
|
Rundir string `toml:"rundir"`
|
|
uuid string
|
|
}
|
|
|
|
func (g *gdProcess) updateDirs(t *testing.T) {
|
|
g.Rundir = path.Clean(g.Rundir)
|
|
if !path.IsAbs(g.Rundir) {
|
|
g.Rundir = path.Join(baseLocalStateDir, t.Name(), g.Rundir)
|
|
}
|
|
g.LocalStateDir = path.Clean(g.LocalStateDir)
|
|
if !path.IsAbs(g.LocalStateDir) {
|
|
g.LocalStateDir = path.Join(baseLocalStateDir, t.Name(), g.LocalStateDir)
|
|
}
|
|
}
|
|
|
|
func (g *gdProcess) EraseLocalStateDir() error {
|
|
return os.RemoveAll(g.LocalStateDir)
|
|
}
|
|
|
|
func (g *gdProcess) PeerID() string {
|
|
|
|
if g.uuid != "" {
|
|
return g.uuid
|
|
}
|
|
|
|
// Endpoint doesn't matter here. All responses include a
|
|
// X-Gluster-Peer-Id response header.
|
|
endpoint := fmt.Sprintf("http://%s/version", g.ClientAddress)
|
|
resp, err := http.Get(endpoint)
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
g.uuid = resp.Header.Get("X-Gluster-Peer-Id")
|
|
return g.uuid
|
|
}
|
|
|
|
func (g *gdProcess) IsRestServerUp(t *testing.T) bool {
|
|
|
|
hc := &http.Client{
|
|
Timeout: 5 * time.Second,
|
|
}
|
|
|
|
endpoint := fmt.Sprintf("http://%s/v1/peers", g.ClientAddress)
|
|
resp, err := hc.Get(endpoint)
|
|
if err != nil {
|
|
t.Logf("IsRestServerUp(): Get failed: %s", err.Error())
|
|
return false
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode/100 == 5 {
|
|
var body string
|
|
b, err := ioutil.ReadAll(resp.Body)
|
|
if err != nil {
|
|
t.Logf("IsRestServerUp(): ioutil.ReadAll() failed: %s", err.Error())
|
|
} else {
|
|
body = string(b)
|
|
}
|
|
t.Logf("IsRestServerUp(): Get failed. StatusCode=%d;Body=%s",
|
|
resp.StatusCode, body)
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
func spawnGlusterd(t *testing.T, configFilePath string, cleanStart bool) (*gdProcess, error) {
|
|
|
|
fContent, err := ioutil.ReadFile(configFilePath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
g := gdProcess{}
|
|
if err = toml.Unmarshal(fContent, &g); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// The config files in e2e/config contain relative paths, convert them
|
|
// to absolute paths.
|
|
g.updateDirs(t)
|
|
|
|
if cleanStart {
|
|
g.EraseLocalStateDir() // cleanup leftovers from previous test
|
|
}
|
|
|
|
if err := os.MkdirAll(path.Join(g.LocalStateDir, "log"), os.ModeDir|os.ModePerm); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err := os.MkdirAll(g.Rundir, os.ModeDir|os.ModePerm); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
absConfigFilePath, err := filepath.Abs(configFilePath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
args := []string{
|
|
"--config", absConfigFilePath,
|
|
"--localstatedir", g.LocalStateDir,
|
|
"--rundir", g.Rundir,
|
|
"--logdir", path.Join(g.LocalStateDir, "log"),
|
|
"--logfile", "glusterd2.log",
|
|
}
|
|
if externalEtcd {
|
|
args = append(args,
|
|
"--noembed",
|
|
// non-default port to avoid conflict with "real" etcd
|
|
// on system
|
|
// TODO: dynamic ports to avoid test conflicts?
|
|
"--etcdendpoints", "http://localhost:22379",
|
|
)
|
|
}
|
|
g.Cmd = exec.Command(path.Join(binDir, "glusterd2"), args...)
|
|
|
|
if err := g.Cmd.Start(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
go func() {
|
|
g.Cmd.Wait()
|
|
}()
|
|
|
|
retries := 4
|
|
waitTime := 3000
|
|
for i := 0; i < retries; i++ {
|
|
// opposite of exponential backoff
|
|
time.Sleep(time.Duration(waitTime) * time.Millisecond)
|
|
if g.IsRestServerUp(t) {
|
|
break
|
|
}
|
|
waitTime = waitTime / 2
|
|
}
|
|
|
|
if !g.IsRestServerUp(t) {
|
|
return nil, fmt.Errorf("timeout: could not query gd2 (%s) rest server", g.PeerID())
|
|
}
|
|
|
|
return &g, nil
|
|
}
|
|
|
|
// etcdProcess is used to manage etcd processes by the test suite
|
|
// when glusterd2 is not running it's own embedded etcd.
|
|
type etcdProcess struct {
|
|
testProcess
|
|
DataDir string
|
|
LogPath string
|
|
}
|
|
|
|
// Spawn starts a new etcd instance.
|
|
func (ep *etcdProcess) Spawn() error {
|
|
args := []string{
|
|
"--name", "e2e-test-etcd",
|
|
"--data-dir", ep.DataDir,
|
|
"--listen-client-urls", "http://localhost:22379",
|
|
"--advertise-client-urls", "http://localhost:22379",
|
|
"--log-output", "stdout",
|
|
}
|
|
ep.Cmd = exec.Command("etcd", args...)
|
|
|
|
var (
|
|
logf *os.File
|
|
err error
|
|
)
|
|
if ep.LogPath != "" {
|
|
logf, err = os.OpenFile(ep.LogPath,
|
|
os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
ep.Cmd.Stdout = logf
|
|
// only the start function needs the open fd. Once the child
|
|
// process has the fd, we want to close the one we opened
|
|
// (on all exit paths).
|
|
defer logf.Close()
|
|
}
|
|
|
|
if err := ep.Cmd.Start(); err != nil {
|
|
return err
|
|
}
|
|
|
|
go func() {
|
|
ep.Cmd.Wait()
|
|
}()
|
|
// TODO: liveness check?
|
|
return nil
|
|
}
|