*: remove unused pkgs

This commit is contained in:
Xiang Li
2014-09-02 21:36:14 -07:00
committed by Yicheng Qin
parent 03174c9361
commit 2ba57ee75d
62 changed files with 10 additions and 9605 deletions

View File

@ -1,37 +0,0 @@
package conf
// ClusterConfig represents cluster-wide configuration settings.
type ClusterConfig struct {
// ActiveSize is the maximum number of node that can join as Raft followers.
// Nodes that join the cluster after the limit is reached are standbys.
ActiveSize int `json:"activeSize"`
// RemoveDelay is the amount of time, in seconds, after a node is
// unreachable that it will be swapped out as a standby node.
RemoveDelay float64 `json:"removeDelay"`
// SyncInterval is the amount of time, in seconds, between
// cluster sync when it runs in standby mode.
SyncInterval float64 `json:"syncInterval"`
}
// NewClusterConfig returns a cluster configuration with default settings.
func NewClusterConfig() *ClusterConfig {
return &ClusterConfig{
ActiveSize: DefaultActiveSize,
RemoveDelay: DefaultRemoveDelay,
SyncInterval: DefaultSyncInterval,
}
}
func (c *ClusterConfig) Sanitize() {
if c.ActiveSize < MinActiveSize {
c.ActiveSize = MinActiveSize
}
if c.RemoveDelay < MinRemoveDelay {
c.RemoveDelay = MinRemoveDelay
}
if c.SyncInterval < MinSyncInterval {
c.SyncInterval = MinSyncInterval
}
}

View File

@ -1,426 +0,0 @@
package conf
import (
"flag"
"fmt"
"io/ioutil"
"log"
"math/rand"
"net"
"net/url"
"os"
"path/filepath"
"reflect"
"strconv"
"strings"
"time"
)
// The default location for the etcd configuration file.
const DefaultSystemConfigPath = "/etc/etcd/etcd.conf"
// Config represents the server configuration.
type Config struct {
SystemPath string
Addr string `env:"ETCD_ADDR"`
BindAddr string `env:"ETCD_BIND_ADDR"`
CAFile string `env:"ETCD_CA_FILE"`
CertFile string `env:"ETCD_CERT_FILE"`
CPUProfileFile string
CorsOrigins []string `env:"ETCD_CORS"`
DataDir string `env:"ETCD_DATA_DIR"`
Discovery string `env:"ETCD_DISCOVERY"`
Force bool
KeyFile string `env:"ETCD_KEY_FILE"`
HTTPReadTimeout float64 `env:"ETCD_HTTP_READ_TIMEOUT"`
HTTPWriteTimeout float64 `env:"ETCD_HTTP_WRITE_TIMEOUT"`
Peers []string `env:"ETCD_PEERS"`
PeersFile string `env:"ETCD_PEERS_FILE"`
MaxResultBuffer int `env:"ETCD_MAX_RESULT_BUFFER"`
MaxRetryAttempts int `env:"ETCD_MAX_RETRY_ATTEMPTS"`
RetryInterval float64 `env:"ETCD_RETRY_INTERVAL"`
Name string `env:"ETCD_NAME"`
Snapshot bool `env:"ETCD_SNAPSHOT"`
SnapshotCount int `env:"ETCD_SNAPSHOTCOUNT"`
ShowHelp bool
ShowVersion bool
Verbose bool `env:"ETCD_VERBOSE"`
VeryVerbose bool `env:"ETCD_VERY_VERBOSE"`
VeryVeryVerbose bool `env:"ETCD_VERY_VERY_VERBOSE"`
Peer struct {
Addr string `env:"ETCD_PEER_ADDR"`
BindAddr string `env:"ETCD_PEER_BIND_ADDR"`
CAFile string `env:"ETCD_PEER_CA_FILE"`
CertFile string `env:"ETCD_PEER_CERT_FILE"`
KeyFile string `env:"ETCD_PEER_KEY_FILE"`
HeartbeatInterval int `env:"ETCD_PEER_HEARTBEAT_INTERVAL"`
ElectionTimeout int `env:"ETCD_PEER_ELECTION_TIMEOUT"`
}
strTrace string `env:"ETCD_TRACE"`
GraphiteHost string `env:"ETCD_GRAPHITE_HOST"`
Cluster struct {
ActiveSize int `env:"ETCD_CLUSTER_ACTIVE_SIZE"`
RemoveDelay float64 `env:"ETCD_CLUSTER_REMOVE_DELAY"`
SyncInterval float64 `env:"ETCD_CLUSTER_SYNC_INTERVAL"`
}
}
// New returns a Config initialized with default values.
func New() *Config {
c := new(Config)
c.SystemPath = DefaultSystemConfigPath
c.Addr = "127.0.0.1:4001"
c.HTTPReadTimeout = DefaultReadTimeout
c.HTTPWriteTimeout = DefaultWriteTimeout
c.MaxResultBuffer = 1024
c.MaxRetryAttempts = 3
c.RetryInterval = 10.0
c.Snapshot = true
c.SnapshotCount = 10000
c.Peer.Addr = "127.0.0.1:7001"
c.Peer.HeartbeatInterval = DefaultHeartbeatInterval
c.Peer.ElectionTimeout = DefaultElectionTimeout
rand.Seed(time.Now().UTC().UnixNano())
// Make maximum twice as minimum.
c.RetryInterval = float64(50+rand.Int()%50) * DefaultHeartbeatInterval / 1000
c.Cluster.ActiveSize = DefaultActiveSize
c.Cluster.RemoveDelay = DefaultRemoveDelay
c.Cluster.SyncInterval = DefaultSyncInterval
return c
}
// Loads the configuration from the system config, command line config,
// environment variables, and finally command line arguments.
func (c *Config) Load(arguments []string) error {
var path string
f := flag.NewFlagSet("etcd", -1)
f.SetOutput(ioutil.Discard)
f.StringVar(&path, "config", "", "path to config file")
f.Parse(arguments)
// Load from the environment variables next.
if err := c.LoadEnv(); err != nil {
return err
}
// Load from command line flags.
if err := c.LoadFlags(arguments); err != nil {
return err
}
// Loads peers if a peer file was specified.
if err := c.LoadPeersFile(); err != nil {
return err
}
return nil
}
// LoadEnv loads the configuration via environment variables.
func (c *Config) LoadEnv() error {
if err := c.loadEnv(c); err != nil {
return err
}
if err := c.loadEnv(&c.Peer); err != nil {
return err
}
if err := c.loadEnv(&c.Cluster); err != nil {
return err
}
return nil
}
func (c *Config) loadEnv(target interface{}) error {
value := reflect.Indirect(reflect.ValueOf(target))
typ := value.Type()
for i := 0; i < typ.NumField(); i++ {
field := typ.Field(i)
// Retrieve environment variable.
v := strings.TrimSpace(os.Getenv(field.Tag.Get("env")))
if v == "" {
continue
}
// Set the appropriate type.
switch field.Type.Kind() {
case reflect.Bool:
value.Field(i).SetBool(v != "0" && v != "false")
case reflect.Int:
newValue, err := strconv.ParseInt(v, 10, 0)
if err != nil {
return fmt.Errorf("Parse error: %s: %s", field.Tag.Get("env"), err)
}
value.Field(i).SetInt(newValue)
case reflect.String:
value.Field(i).SetString(v)
case reflect.Slice:
value.Field(i).Set(reflect.ValueOf(trimSplit(v, ",")))
case reflect.Float64:
newValue, err := strconv.ParseFloat(v, 64)
if err != nil {
return fmt.Errorf("Parse error: %s: %s", field.Tag.Get("env"), err)
}
value.Field(i).SetFloat(newValue)
}
}
return nil
}
// Loads configuration from command line flags.
func (c *Config) LoadFlags(arguments []string) error {
var peers, cors, path string
f := flag.NewFlagSet(os.Args[0], flag.ContinueOnError)
f.SetOutput(ioutil.Discard)
f.BoolVar(&c.ShowHelp, "h", false, "")
f.BoolVar(&c.ShowHelp, "help", false, "")
f.BoolVar(&c.ShowVersion, "version", false, "")
f.BoolVar(&c.Force, "f", false, "")
f.BoolVar(&c.Force, "force", false, "")
f.BoolVar(&c.Verbose, "v", c.Verbose, "")
f.BoolVar(&c.VeryVerbose, "vv", c.VeryVerbose, "")
f.BoolVar(&c.VeryVeryVerbose, "vvv", c.VeryVeryVerbose, "")
f.StringVar(&peers, "peers", "", "")
f.StringVar(&c.PeersFile, "peers-file", c.PeersFile, "")
f.StringVar(&c.Name, "name", c.Name, "")
f.StringVar(&c.Addr, "addr", c.Addr, "")
f.StringVar(&c.Discovery, "discovery", c.Discovery, "")
f.StringVar(&c.BindAddr, "bind-addr", c.BindAddr, "")
f.StringVar(&c.Peer.Addr, "peer-addr", c.Peer.Addr, "")
f.StringVar(&c.Peer.BindAddr, "peer-bind-addr", c.Peer.BindAddr, "")
f.StringVar(&c.CAFile, "ca-file", c.CAFile, "")
f.StringVar(&c.CertFile, "cert-file", c.CertFile, "")
f.StringVar(&c.KeyFile, "key-file", c.KeyFile, "")
f.StringVar(&c.Peer.CAFile, "peer-ca-file", c.Peer.CAFile, "")
f.StringVar(&c.Peer.CertFile, "peer-cert-file", c.Peer.CertFile, "")
f.StringVar(&c.Peer.KeyFile, "peer-key-file", c.Peer.KeyFile, "")
f.Float64Var(&c.HTTPReadTimeout, "http-read-timeout", c.HTTPReadTimeout, "")
f.Float64Var(&c.HTTPWriteTimeout, "http-write-timeout", c.HTTPReadTimeout, "")
f.StringVar(&c.DataDir, "data-dir", c.DataDir, "")
f.IntVar(&c.MaxResultBuffer, "max-result-buffer", c.MaxResultBuffer, "")
f.IntVar(&c.MaxRetryAttempts, "max-retry-attempts", c.MaxRetryAttempts, "")
f.Float64Var(&c.RetryInterval, "retry-interval", c.RetryInterval, "")
f.IntVar(&c.Peer.HeartbeatInterval, "peer-heartbeat-interval", c.Peer.HeartbeatInterval, "")
f.IntVar(&c.Peer.ElectionTimeout, "peer-election-timeout", c.Peer.ElectionTimeout, "")
f.StringVar(&cors, "cors", "", "")
f.BoolVar(&c.Snapshot, "snapshot", c.Snapshot, "")
f.IntVar(&c.SnapshotCount, "snapshot-count", c.SnapshotCount, "")
f.StringVar(&c.CPUProfileFile, "cpuprofile", "", "")
f.StringVar(&c.strTrace, "trace", "", "")
f.StringVar(&c.GraphiteHost, "graphite-host", "", "")
f.IntVar(&c.Cluster.ActiveSize, "cluster-active-size", c.Cluster.ActiveSize, "")
f.Float64Var(&c.Cluster.RemoveDelay, "cluster-remove-delay", c.Cluster.RemoveDelay, "")
f.Float64Var(&c.Cluster.SyncInterval, "cluster-sync-interval", c.Cluster.SyncInterval, "")
// BEGIN IGNORED FLAGS
f.StringVar(&path, "config", "", "")
// BEGIN IGNORED FLAGS
if err := f.Parse(arguments); err != nil {
return err
}
// Convert some parameters to lists.
if peers != "" {
c.Peers = trimSplit(peers, ",")
}
if cors != "" {
c.CorsOrigins = trimSplit(cors, ",")
}
return nil
}
// LoadPeersFile loads the peers listed in the peers file.
func (c *Config) LoadPeersFile() error {
if c.PeersFile == "" {
return nil
}
b, err := ioutil.ReadFile(c.PeersFile)
if err != nil {
return fmt.Errorf("Peers file error: %s", err)
}
c.Peers = trimSplit(string(b), ",")
return nil
}
// DataDirFromName sets the data dir from a machine name and issue a warning
// that etcd is guessing.
func (c *Config) DataDirFromName() {
c.DataDir = c.Name + ".etcd"
log.Printf("Using the directory %s as the etcd curation directory because a directory was not specified. ", c.DataDir)
return
}
// NameFromHostname sets the machine name from the hostname. This is to help
// people get started without thinking up a name.
func (c *Config) NameFromHostname() {
host, err := os.Hostname()
if err != nil && host == "" {
log.Fatal("Node name required and hostname not set. e.g. '-name=name'")
}
c.Name = host
}
// Reset removes all server configuration files.
func (c *Config) Reset() error {
if err := os.RemoveAll(filepath.Join(c.DataDir, "log")); err != nil {
return err
}
if err := os.RemoveAll(filepath.Join(c.DataDir, "conf")); err != nil {
return err
}
if err := os.RemoveAll(filepath.Join(c.DataDir, "snapshot")); err != nil {
return err
}
if err := os.RemoveAll(filepath.Join(c.DataDir, "standby_info")); err != nil {
return err
}
return nil
}
// Sanitize cleans the input fields.
func (c *Config) Sanitize() error {
var err error
var url *url.URL
// Sanitize the URLs first.
if c.Addr, url, err = sanitizeURL(c.Addr, c.EtcdTLSInfo().Scheme()); err != nil {
return fmt.Errorf("Advertised URL: %s", err)
}
if c.BindAddr, err = sanitizeBindAddr(c.BindAddr, url); err != nil {
return fmt.Errorf("Listen Host: %s", err)
}
if c.Peer.Addr, url, err = sanitizeURL(c.Peer.Addr, c.PeerTLSInfo().Scheme()); err != nil {
return fmt.Errorf("Peer Advertised URL: %s", err)
}
if c.Peer.BindAddr, err = sanitizeBindAddr(c.Peer.BindAddr, url); err != nil {
return fmt.Errorf("Peer Listen Host: %s", err)
}
// Only guess the machine name if there is no data dir specified
// because the info file should have our name
if c.Name == "" && c.DataDir == "" {
c.NameFromHostname()
}
if c.DataDir == "" && c.Name != "" && !c.ShowVersion && !c.ShowHelp {
c.DataDirFromName()
}
return nil
}
// EtcdTLSInfo retrieves a TLSInfo object for the etcd server
func (c *Config) EtcdTLSInfo() *TLSInfo {
return &TLSInfo{
CAFile: c.CAFile,
CertFile: c.CertFile,
KeyFile: c.KeyFile,
}
}
// PeerRaftInfo retrieves a TLSInfo object for the peer server.
func (c *Config) PeerTLSInfo() *TLSInfo {
return &TLSInfo{
CAFile: c.Peer.CAFile,
CertFile: c.Peer.CertFile,
KeyFile: c.Peer.KeyFile,
}
}
// MetricsBucketName generates the name that should be used for a
// corresponding MetricsBucket object
func (c *Config) MetricsBucketName() string {
return fmt.Sprintf("etcd.%s", c.Name)
}
// Trace determines if any trace-level information should be emitted
func (c *Config) Trace() bool {
return c.strTrace == "*"
}
func (c *Config) ClusterConfig() *ClusterConfig {
return &ClusterConfig{
ActiveSize: c.Cluster.ActiveSize,
RemoveDelay: c.Cluster.RemoveDelay,
SyncInterval: c.Cluster.SyncInterval,
}
}
// sanitizeURL will cleanup a host string in the format hostname[:port] and
// attach a schema.
func sanitizeURL(host string, defaultScheme string) (string, *url.URL, error) {
// Blank URLs are fine input, just return it
if len(host) == 0 {
return host, &url.URL{}, nil
}
p, err := url.Parse(host)
if err != nil {
return "", nil, err
}
// Make sure the host is in Host:Port format
_, _, err = net.SplitHostPort(host)
if err != nil {
return "", nil, err
}
p = &url.URL{Host: host, Scheme: defaultScheme}
return p.String(), p, nil
}
// sanitizeBindAddr cleans up the BindAddr parameter and appends a port
// if necessary based on the advertised port.
func sanitizeBindAddr(bindAddr string, aurl *url.URL) (string, error) {
// If it is a valid host:port simply return with no further checks.
bhost, bport, err := net.SplitHostPort(bindAddr)
if err == nil && bhost != "" {
return bindAddr, nil
}
// SplitHostPort makes the host optional, but we don't want that.
if bhost == "" && bport != "" {
return "", fmt.Errorf("IP required can't use a port only")
}
// bindAddr doesn't have a port if we reach here so take the port from the
// advertised URL.
_, aport, err := net.SplitHostPort(aurl.Host)
if err != nil {
return "", err
}
return net.JoinHostPort(bindAddr, aport), nil
}
// trimSplit slices s into all substrings separated by sep and returns a
// slice of the substrings between the separator with all leading and trailing
// white space removed, as defined by Unicode.
func trimSplit(s, sep string) []string {
trimmed := strings.Split(s, sep)
for i := range trimmed {
trimmed[i] = strings.TrimSpace(trimmed[i])
}
return trimmed
}

View File

@ -1,572 +0,0 @@
package conf
import (
"io/ioutil"
"os"
"testing"
"github.com/coreos/etcd/third_party/github.com/stretchr/testify/assert"
)
// Ensures that a configuration can be retrieved from environment variables.
func TestConfigEnv(t *testing.T) {
os.Setenv("ETCD_CA_FILE", "/tmp/file.ca")
os.Setenv("ETCD_CERT_FILE", "/tmp/file.cert")
os.Setenv("ETCD_CPU_PROFILE_FILE", "XXX")
os.Setenv("ETCD_CORS", "localhost:4001,localhost:4002")
os.Setenv("ETCD_DATA_DIR", "/tmp/data")
os.Setenv("ETCD_DISCOVERY", "http://example.com/foobar")
os.Setenv("ETCD_HTTP_READ_TIMEOUT", "2.34")
os.Setenv("ETCD_HTTP_WRITE_TIMEOUT", "1.23")
os.Setenv("ETCD_KEY_FILE", "/tmp/file.key")
os.Setenv("ETCD_BIND_ADDR", "127.0.0.1:4003")
os.Setenv("ETCD_PEERS", "coreos.com:4001,coreos.com:4002")
os.Setenv("ETCD_PEERS_FILE", "/tmp/peers")
os.Setenv("ETCD_MAX_CLUSTER_SIZE", "10")
os.Setenv("ETCD_MAX_RESULT_BUFFER", "512")
os.Setenv("ETCD_MAX_RETRY_ATTEMPTS", "5")
os.Setenv("ETCD_NAME", "test-name")
os.Setenv("ETCD_SNAPSHOT", "true")
os.Setenv("ETCD_VERBOSE", "1")
os.Setenv("ETCD_VERY_VERBOSE", "yes")
os.Setenv("ETCD_PEER_ADDR", "127.0.0.1:7002")
os.Setenv("ETCD_PEER_CA_FILE", "/tmp/peer/file.ca")
os.Setenv("ETCD_PEER_CERT_FILE", "/tmp/peer/file.cert")
os.Setenv("ETCD_PEER_KEY_FILE", "/tmp/peer/file.key")
os.Setenv("ETCD_PEER_BIND_ADDR", "127.0.0.1:7003")
os.Setenv("ETCD_CLUSTER_ACTIVE_SIZE", "5")
os.Setenv("ETCD_CLUSTER_REMOVE_DELAY", "100")
os.Setenv("ETCD_CLUSTER_SYNC_INTERVAL", "10")
c := New()
c.LoadEnv()
assert.Equal(t, c.CAFile, "/tmp/file.ca", "")
assert.Equal(t, c.CertFile, "/tmp/file.cert", "")
assert.Equal(t, c.CorsOrigins, []string{"localhost:4001", "localhost:4002"}, "")
assert.Equal(t, c.DataDir, "/tmp/data", "")
assert.Equal(t, c.Discovery, "http://example.com/foobar", "")
assert.Equal(t, c.HTTPReadTimeout, 2.34, "")
assert.Equal(t, c.HTTPWriteTimeout, 1.23, "")
assert.Equal(t, c.KeyFile, "/tmp/file.key", "")
assert.Equal(t, c.BindAddr, "127.0.0.1:4003", "")
assert.Equal(t, c.Peers, []string{"coreos.com:4001", "coreos.com:4002"}, "")
assert.Equal(t, c.PeersFile, "/tmp/peers", "")
assert.Equal(t, c.MaxResultBuffer, 512, "")
assert.Equal(t, c.MaxRetryAttempts, 5, "")
assert.Equal(t, c.Name, "test-name", "")
assert.Equal(t, c.Snapshot, true, "")
assert.Equal(t, c.Verbose, true, "")
assert.Equal(t, c.VeryVerbose, true, "")
assert.Equal(t, c.Peer.Addr, "127.0.0.1:7002", "")
assert.Equal(t, c.Peer.CAFile, "/tmp/peer/file.ca", "")
assert.Equal(t, c.Peer.CertFile, "/tmp/peer/file.cert", "")
assert.Equal(t, c.Peer.KeyFile, "/tmp/peer/file.key", "")
assert.Equal(t, c.Peer.BindAddr, "127.0.0.1:7003", "")
assert.Equal(t, c.Cluster.ActiveSize, 5, "")
assert.Equal(t, c.Cluster.RemoveDelay, 100.0, "")
assert.Equal(t, c.Cluster.SyncInterval, 10.0, "")
// Clear this as it will mess up other tests
os.Setenv("ETCD_DISCOVERY", "")
}
// Ensures that the "help" flag can be parsed.
func TestConfigHelpFlag(t *testing.T) {
c := New()
assert.Nil(t, c.LoadFlags([]string{"-help"}), "")
assert.True(t, c.ShowHelp)
}
// Ensures that the abbreviated "help" flag can be parsed.
func TestConfigAbbreviatedHelpFlag(t *testing.T) {
c := New()
assert.Nil(t, c.LoadFlags([]string{"-h"}), "")
assert.True(t, c.ShowHelp)
}
// Ensures that the "version" flag can be parsed.
func TestConfigVersionFlag(t *testing.T) {
c := New()
assert.Nil(t, c.LoadFlags([]string{"-version"}), "")
assert.True(t, c.ShowVersion)
}
// Ensures that the "force config" flag can be parsed.
func TestConfigForceFlag(t *testing.T) {
c := New()
assert.Nil(t, c.LoadFlags([]string{"-force"}), "")
assert.True(t, c.Force)
}
// Ensures that the abbreviated "force config" flag can be parsed.
func TestConfigAbbreviatedForceFlag(t *testing.T) {
c := New()
assert.Nil(t, c.LoadFlags([]string{"-f"}), "")
assert.True(t, c.Force)
}
// Ensures that the advertised url can be parsed from the environment.
func TestConfigAddrEnv(t *testing.T) {
withEnv("ETCD_ADDR", "127.0.0.1:4002", func(c *Config) {
assert.Nil(t, c.LoadEnv(), "")
assert.Equal(t, c.Addr, "127.0.0.1:4002", "")
})
}
// Ensures that the advertised flag can be parsed.
func TestConfigAddrFlag(t *testing.T) {
c := New()
assert.Nil(t, c.LoadFlags([]string{"-addr", "127.0.0.1:4002"}), "")
assert.Equal(t, c.Addr, "127.0.0.1:4002", "")
}
// Ensures that the CA file can be parsed from the environment.
func TestConfigCAFileEnv(t *testing.T) {
withEnv("ETCD_CA_FILE", "/tmp/file.ca", func(c *Config) {
assert.Nil(t, c.LoadEnv(), "")
assert.Equal(t, c.CAFile, "/tmp/file.ca", "")
})
}
// Ensures that the CA file flag can be parsed.
func TestConfigCAFileFlag(t *testing.T) {
c := New()
assert.Nil(t, c.LoadFlags([]string{"-ca-file", "/tmp/file.ca"}), "")
assert.Equal(t, c.CAFile, "/tmp/file.ca", "")
}
// Ensures that the CA file can be parsed from the environment.
func TestConfigCertFileEnv(t *testing.T) {
withEnv("ETCD_CERT_FILE", "/tmp/file.cert", func(c *Config) {
assert.Nil(t, c.LoadEnv(), "")
assert.Equal(t, c.CertFile, "/tmp/file.cert", "")
})
}
// Ensures that the Cert file flag can be parsed.
func TestConfigCertFileFlag(t *testing.T) {
c := New()
assert.Nil(t, c.LoadFlags([]string{"-cert-file", "/tmp/file.cert"}), "")
assert.Equal(t, c.CertFile, "/tmp/file.cert", "")
}
// Ensures that the Key file can be parsed from the environment.
func TestConfigKeyFileEnv(t *testing.T) {
withEnv("ETCD_KEY_FILE", "/tmp/file.key", func(c *Config) {
assert.Nil(t, c.LoadEnv(), "")
assert.Equal(t, c.KeyFile, "/tmp/file.key", "")
})
}
// Ensures that the Key file flag can be parsed.
func TestConfigKeyFileFlag(t *testing.T) {
c := New()
assert.Nil(t, c.LoadFlags([]string{"-key-file", "/tmp/file.key"}), "")
assert.Equal(t, c.KeyFile, "/tmp/file.key", "")
}
// Ensures that the Listen Host can be parsed from the environment.
func TestConfigBindAddrEnv(t *testing.T) {
withEnv("ETCD_BIND_ADDR", "127.0.0.1:4003", func(c *Config) {
assert.Nil(t, c.LoadEnv(), "")
assert.Equal(t, c.BindAddr, "127.0.0.1:4003", "")
})
}
// Ensures that the Listen Host file flag can be parsed.
func TestConfigBindAddrFlag(t *testing.T) {
c := New()
assert.Nil(t, c.LoadFlags([]string{"-bind-addr", "127.0.0.1:4003"}), "")
assert.Equal(t, c.BindAddr, "127.0.0.1:4003", "")
}
// Ensures that the Listen Host port overrides the advertised port
func TestConfigBindAddrOverride(t *testing.T) {
c := New()
assert.Nil(t, c.LoadFlags([]string{"-addr", "127.0.0.1:4009", "-bind-addr", "127.0.0.1:4010"}), "")
assert.Nil(t, c.Sanitize())
assert.Equal(t, c.BindAddr, "127.0.0.1:4010", "")
}
// Ensures that the Listen Host port overrides the advertised port
func TestConfigBindIPv6AddrOverride(t *testing.T) {
c := New()
assert.Nil(t, c.LoadFlags([]string{"-addr", "[::1]:4009", "-bind-addr", "[::1]:4010"}), "")
assert.Nil(t, c.Sanitize())
assert.Equal(t, c.BindAddr, "[::1]:4010", "")
}
// Ensures that the Listen Host port overrides the advertised port
func TestConfigBindIPv6WithZoneAddrOverride(t *testing.T) {
c := New()
assert.Nil(t, c.LoadFlags([]string{"-addr", "[::1%25lo]:4009", "-bind-addr", "[::1%25lo]:4010"}), "")
assert.Nil(t, c.Sanitize())
assert.Equal(t, c.BindAddr, "[::1%25lo]:4010", "")
}
// Ensures that the Listen Host inherits its port from the advertised addr
func TestConfigBindAddrInheritPort(t *testing.T) {
c := New()
assert.Nil(t, c.LoadFlags([]string{"-addr", "127.0.0.1:4009", "-bind-addr", "127.0.0.1"}), "")
assert.Nil(t, c.Sanitize())
assert.Equal(t, c.BindAddr, "127.0.0.1:4009", "")
}
// Ensures that the Listen Host inherits its port from the advertised addr
func TestConfigBindIPv6AddrInheritPort(t *testing.T) {
c := New()
assert.Nil(t, c.LoadFlags([]string{"-addr", "[::1]:4009", "-bind-addr", "::1"}), "")
assert.Nil(t, c.Sanitize())
assert.Equal(t, c.BindAddr, "[::1]:4009", "")
}
// Ensures that the Listen Host inherits its port from the advertised addr
func TestConfigBindIPv6WithZoneAddrInheritPort(t *testing.T) {
c := New()
assert.Nil(t, c.LoadFlags([]string{"-addr", "[::1%25lo]:4009", "-bind-addr", "::1%25lo"}), "")
assert.Nil(t, c.Sanitize())
assert.Equal(t, c.BindAddr, "[::1%25lo]:4009", "")
}
// Ensures that a port only argument errors out
func TestConfigBindAddrErrorOnNoHost(t *testing.T) {
c := New()
assert.Nil(t, c.LoadFlags([]string{"-addr", "127.0.0.1:4009", "-bind-addr", ":4010"}), "")
assert.Error(t, c.Sanitize())
}
// Ensures that a bad IPv6 address will raise an error
func TestConfigBindAddrErrorOnBadIPv6Addr(t *testing.T) {
c := New()
assert.Nil(t, c.LoadFlags([]string{"-addr", "[::1%lo]:4009"}), "")
assert.Error(t, c.Sanitize())
}
// Ensures that the peers can be parsed from the environment.
func TestConfigPeersEnv(t *testing.T) {
withEnv("ETCD_PEERS", "coreos.com:4001,coreos.com:4002", func(c *Config) {
assert.Nil(t, c.LoadEnv(), "")
assert.Equal(t, c.Peers, []string{"coreos.com:4001", "coreos.com:4002"}, "")
})
}
// Ensures that the Peers flag can be parsed.
func TestConfigPeersFlag(t *testing.T) {
c := New()
assert.Nil(t, c.LoadFlags([]string{"-peers", "coreos.com:4001,coreos.com:4002"}), "")
assert.Equal(t, c.Peers, []string{"coreos.com:4001", "coreos.com:4002"}, "")
}
// Ensures that the Peers File can be parsed from the environment.
func TestConfigPeersFileEnv(t *testing.T) {
withEnv("ETCD_PEERS_FILE", "/tmp/peers", func(c *Config) {
assert.Nil(t, c.LoadEnv(), "")
assert.Equal(t, c.PeersFile, "/tmp/peers", "")
})
}
// Ensures that the Peers File flag can be parsed.
func TestConfigPeersFileFlag(t *testing.T) {
c := New()
assert.Nil(t, c.LoadFlags([]string{"-peers-file", "/tmp/peers"}), "")
assert.Equal(t, c.PeersFile, "/tmp/peers", "")
}
// Ensures that the Max Result Buffer can be parsed from the environment.
func TestConfigMaxResultBufferEnv(t *testing.T) {
withEnv("ETCD_MAX_RESULT_BUFFER", "512", func(c *Config) {
assert.Nil(t, c.LoadEnv(), "")
assert.Equal(t, c.MaxResultBuffer, 512, "")
})
}
// Ensures that the Max Result Buffer flag can be parsed.
func TestConfigMaxResultBufferFlag(t *testing.T) {
c := New()
assert.Nil(t, c.LoadFlags([]string{"-max-result-buffer", "512"}), "")
assert.Equal(t, c.MaxResultBuffer, 512, "")
}
// Ensures that the Max Retry Attempts can be parsed from the environment.
func TestConfigMaxRetryAttemptsEnv(t *testing.T) {
withEnv("ETCD_MAX_RETRY_ATTEMPTS", "10", func(c *Config) {
assert.Nil(t, c.LoadEnv(), "")
assert.Equal(t, c.MaxRetryAttempts, 10, "")
})
}
// Ensures that the Max Retry Attempts flag can be parsed.
func TestConfigMaxRetryAttemptsFlag(t *testing.T) {
c := New()
assert.Nil(t, c.LoadFlags([]string{"-max-retry-attempts", "10"}), "")
assert.Equal(t, c.MaxRetryAttempts, 10, "")
}
// Ensures that the Name can be parsed from the environment.
func TestConfigNameEnv(t *testing.T) {
withEnv("ETCD_NAME", "test-name", func(c *Config) {
assert.Nil(t, c.LoadEnv(), "")
assert.Equal(t, c.Name, "test-name", "")
})
}
// Ensures that the Name flag can be parsed.
func TestConfigNameFlag(t *testing.T) {
c := New()
assert.Nil(t, c.LoadFlags([]string{"-name", "test-name"}), "")
assert.Equal(t, c.Name, "test-name", "")
}
// Ensures that a Name gets guessed if not specified
func TestConfigNameGuess(t *testing.T) {
c := New()
assert.Nil(t, c.LoadFlags([]string{}), "")
assert.Nil(t, c.Sanitize())
name, _ := os.Hostname()
assert.Equal(t, c.Name, name, "")
}
// Ensures that a DataDir gets guessed if not specified
func TestConfigDataDirGuess(t *testing.T) {
c := New()
assert.Nil(t, c.LoadFlags([]string{}), "")
assert.Nil(t, c.Sanitize())
name, _ := os.Hostname()
assert.Equal(t, c.DataDir, name+".etcd", "")
}
// Ensures that Snapshot can be parsed from the environment.
func TestConfigSnapshotEnv(t *testing.T) {
withEnv("ETCD_SNAPSHOT", "1", func(c *Config) {
assert.Nil(t, c.LoadEnv(), "")
assert.Equal(t, c.Snapshot, true, "")
})
}
// Ensures that the Snapshot flag can be parsed.
func TestConfigSnapshotFlag(t *testing.T) {
c := New()
assert.Nil(t, c.LoadFlags([]string{"-snapshot"}), "")
assert.Equal(t, c.Snapshot, true, "")
}
// Ensures that Verbose can be parsed from the environment.
func TestConfigVerboseEnv(t *testing.T) {
withEnv("ETCD_VERBOSE", "true", func(c *Config) {
assert.Nil(t, c.LoadEnv(), "")
assert.Equal(t, c.Verbose, true, "")
})
}
// Ensures that the Verbose flag can be parsed.
func TestConfigVerboseFlag(t *testing.T) {
c := New()
assert.Nil(t, c.LoadFlags([]string{"-v"}), "")
assert.Equal(t, c.Verbose, true, "")
}
// Ensures that Very Verbose can be parsed from the environment.
func TestConfigVeryVerboseEnv(t *testing.T) {
withEnv("ETCD_VERY_VERBOSE", "true", func(c *Config) {
assert.Nil(t, c.LoadEnv(), "")
assert.Equal(t, c.VeryVerbose, true, "")
})
}
// Ensures that the Very Verbose flag can be parsed.
func TestConfigVeryVerboseFlag(t *testing.T) {
c := New()
assert.Nil(t, c.LoadFlags([]string{"-vv"}), "")
assert.Equal(t, c.VeryVerbose, true, "")
}
// Ensures that the Peer Advertised URL can be parsed from the environment.
func TestConfigPeerAddrEnv(t *testing.T) {
withEnv("ETCD_PEER_ADDR", "localhost:7002", func(c *Config) {
assert.Nil(t, c.LoadEnv(), "")
assert.Equal(t, c.Peer.Addr, "localhost:7002", "")
})
}
// Ensures that the Peer Advertised URL flag can be parsed.
func TestConfigPeerAddrFlag(t *testing.T) {
c := New()
assert.Nil(t, c.LoadFlags([]string{"-peer-addr", "localhost:7002"}), "")
assert.Equal(t, c.Peer.Addr, "localhost:7002", "")
}
// Ensures that the Peer CA File can be parsed from the environment.
func TestConfigPeerCAFileEnv(t *testing.T) {
withEnv("ETCD_PEER_CA_FILE", "/tmp/peer/file.ca", func(c *Config) {
assert.Nil(t, c.LoadEnv(), "")
assert.Equal(t, c.Peer.CAFile, "/tmp/peer/file.ca", "")
})
}
// Ensures that the Peer CA file flag can be parsed.
func TestConfigPeerCAFileFlag(t *testing.T) {
c := New()
assert.Nil(t, c.LoadFlags([]string{"-peer-ca-file", "/tmp/peer/file.ca"}), "")
assert.Equal(t, c.Peer.CAFile, "/tmp/peer/file.ca", "")
}
// Ensures that the Peer Cert File can be parsed from the environment.
func TestConfigPeerCertFileEnv(t *testing.T) {
withEnv("ETCD_PEER_CERT_FILE", "/tmp/peer/file.cert", func(c *Config) {
assert.Nil(t, c.LoadEnv(), "")
assert.Equal(t, c.Peer.CertFile, "/tmp/peer/file.cert", "")
})
}
// Ensures that the Cert file flag can be parsed.
func TestConfigPeerCertFileFlag(t *testing.T) {
c := New()
assert.Nil(t, c.LoadFlags([]string{"-peer-cert-file", "/tmp/peer/file.cert"}), "")
assert.Equal(t, c.Peer.CertFile, "/tmp/peer/file.cert", "")
}
// Ensures that the Peer Key File can be parsed from the environment.
func TestConfigPeerKeyFileEnv(t *testing.T) {
withEnv("ETCD_PEER_KEY_FILE", "/tmp/peer/file.key", func(c *Config) {
assert.Nil(t, c.LoadEnv(), "")
assert.Equal(t, c.Peer.KeyFile, "/tmp/peer/file.key", "")
})
}
// Ensures that the Peer Key file flag can be parsed.
func TestConfigPeerKeyFileFlag(t *testing.T) {
c := New()
assert.Nil(t, c.LoadFlags([]string{"-peer-key-file", "/tmp/peer/file.key"}), "")
assert.Equal(t, c.Peer.KeyFile, "/tmp/peer/file.key", "")
}
// Ensures that the Peer Listen Host can be parsed from the environment.
func TestConfigPeerBindAddrEnv(t *testing.T) {
withEnv("ETCD_PEER_BIND_ADDR", "localhost:7004", func(c *Config) {
assert.Nil(t, c.LoadEnv(), "")
assert.Equal(t, c.Peer.BindAddr, "localhost:7004", "")
})
}
// Ensures that a bad flag returns an error.
func TestConfigBadFlag(t *testing.T) {
c := New()
err := c.LoadFlags([]string{"-no-such-flag"})
assert.Error(t, err)
assert.Equal(t, err.Error(), `flag provided but not defined: -no-such-flag`)
}
// Ensures that the Peer Listen Host file flag can be parsed.
func TestConfigPeerBindAddrFlag(t *testing.T) {
c := New()
assert.Nil(t, c.LoadFlags([]string{"-peer-bind-addr", "127.0.0.1:4003"}), "")
assert.Equal(t, c.Peer.BindAddr, "127.0.0.1:4003", "")
}
// Ensures that the cluster active size can be parsed from the environment.
func TestConfigClusterActiveSizeEnv(t *testing.T) {
withEnv("ETCD_CLUSTER_ACTIVE_SIZE", "5", func(c *Config) {
assert.Nil(t, c.LoadEnv(), "")
assert.Equal(t, c.Cluster.ActiveSize, 5, "")
})
}
// Ensures that the cluster active size flag can be parsed.
func TestConfigClusterActiveSizeFlag(t *testing.T) {
c := New()
assert.Nil(t, c.LoadFlags([]string{"-cluster-active-size", "5"}), "")
assert.Equal(t, c.Cluster.ActiveSize, 5, "")
}
// Ensures that the cluster remove delay can be parsed from the environment.
func TestConfigClusterRemoveDelayEnv(t *testing.T) {
withEnv("ETCD_CLUSTER_REMOVE_DELAY", "100", func(c *Config) {
assert.Nil(t, c.LoadEnv(), "")
assert.Equal(t, c.Cluster.RemoveDelay, 100.0, "")
})
}
// Ensures that the cluster remove delay flag can be parsed.
func TestConfigClusterRemoveDelayFlag(t *testing.T) {
c := New()
assert.Nil(t, c.LoadFlags([]string{"-cluster-remove-delay", "100"}), "")
assert.Equal(t, c.Cluster.RemoveDelay, 100.0, "")
}
func TestConfigClusterSyncIntervalFlag(t *testing.T) {
c := New()
assert.Nil(t, c.LoadFlags([]string{"-http-read-timeout", "2.34"}), "")
assert.Equal(t, c.HTTPReadTimeout, 2.34, "")
assert.Nil(t, c.LoadFlags([]string{"-http-write-timeout", "1.23"}), "")
assert.Equal(t, c.HTTPWriteTimeout, 1.23, "")
}
// Ensures that a custom config field is overridden by an environment variable.
func TestConfigEnvVarOverrideCustomConfig(t *testing.T) {
os.Setenv("ETCD_PEER_ADDR", "127.0.0.1:8000")
defer os.Setenv("ETCD_PEER_ADDR", "")
custom := `[peer]` + "\n" + `advertised_url = "127.0.0.1:9000"`
withTempFile(custom, func(path string) {
c := New()
c.SystemPath = ""
assert.Nil(t, c.Load([]string{"-config", path}), "")
assert.Equal(t, c.Peer.Addr, "127.0.0.1:8000", "")
})
}
// Ensures that an environment variable field is overridden by a command line argument.
func TestConfigCLIArgsOverrideEnvVar(t *testing.T) {
os.Setenv("ETCD_ADDR", "127.0.0.1:1000")
defer os.Setenv("ETCD_ADDR", "")
c := New()
c.SystemPath = ""
assert.Nil(t, c.Load([]string{"-addr", "127.0.0.1:2000"}), "")
assert.Equal(t, c.Addr, "127.0.0.1:2000", "")
}
//--------------------------------------
// Helpers
//--------------------------------------
// Sets up the environment with a given environment variable set.
func withEnv(key, value string, f func(c *Config)) {
os.Setenv(key, value)
defer os.Setenv(key, "")
c := New()
f(c)
}
// Creates a temp file and calls a function with the context.
func withTempFile(content string, fn func(string)) {
f, _ := ioutil.TempFile("", "")
f.WriteString(content)
f.Close()
defer os.Remove(f.Name())
fn(f.Name())
}
// Captures STDOUT & STDERR and returns the output as strings.
func capture(fn func()) (string, string) {
// Create temp files.
tmpout, _ := ioutil.TempFile("", "")
defer os.Remove(tmpout.Name())
tmperr, _ := ioutil.TempFile("", "")
defer os.Remove(tmperr.Name())
stdout, stderr := os.Stdout, os.Stderr
os.Stdout, os.Stderr = tmpout, tmperr
// Execute function argument and then reassign stdout/stderr.
fn()
os.Stdout, os.Stderr = stdout, stderr
// Close temp files and read them.
tmpout.Close()
bout, _ := ioutil.ReadFile(tmpout.Name())
tmperr.Close()
berr, _ := ioutil.ReadFile(tmperr.Name())
return string(bout), string(berr)
}

View File

@ -1,32 +0,0 @@
package conf
import "time"
const (
// The amount of time (in ms) to elapse without a heartbeat before becoming a candidate
DefaultElectionTimeout = 200
// The frequency (in ms) by which heartbeats are sent to followers.
DefaultHeartbeatInterval = 50
// DefaultActiveSize is the default number of active followers allowed.
DefaultActiveSize = 9
// MinActiveSize is the minimum active size allowed.
MinActiveSize = 3
// DefaultRemoveDelay is the default elapsed time before removal.
DefaultRemoveDelay = float64((30 * time.Minute) / time.Second)
// MinRemoveDelay is the minimum remove delay allowed.
MinRemoveDelay = float64((2 * time.Second) / time.Second)
// DefaultSyncInterval is the default interval for cluster sync.
DefaultSyncInterval = float64((5 * time.Second) / time.Second)
// MinSyncInterval is the minimum sync interval allowed.
MinSyncInterval = float64((1 * time.Second) / time.Second)
DefaultReadTimeout = float64((5 * time.Minute) / time.Second)
DefaultWriteTimeout = float64((5 * time.Minute) / time.Second)
)

View File

@ -1,105 +0,0 @@
package conf
import (
"crypto/tls"
"crypto/x509"
"encoding/pem"
"fmt"
"io/ioutil"
)
// TLSInfo holds the SSL certificates paths.
type TLSInfo struct {
CertFile string `json:"CertFile"`
KeyFile string `json:"KeyFile"`
CAFile string `json:"CAFile"`
}
func (info TLSInfo) Scheme() string {
if info.KeyFile != "" && info.CertFile != "" {
return "https"
} else {
return "http"
}
}
// Generates a tls.Config object for a server from the given files.
func (info TLSInfo) ServerConfig() (*tls.Config, error) {
// Both the key and cert must be present.
if info.KeyFile == "" || info.CertFile == "" {
return nil, fmt.Errorf("KeyFile and CertFile must both be present[key: %v, cert: %v]", info.KeyFile, info.CertFile)
}
var cfg tls.Config
tlsCert, err := tls.LoadX509KeyPair(info.CertFile, info.KeyFile)
if err != nil {
return nil, err
}
cfg.Certificates = []tls.Certificate{tlsCert}
if info.CAFile != "" {
cfg.ClientAuth = tls.RequireAndVerifyClientCert
cp, err := newCertPool(info.CAFile)
if err != nil {
return nil, err
}
cfg.RootCAs = cp
cfg.ClientCAs = cp
} else {
cfg.ClientAuth = tls.NoClientCert
}
return &cfg, nil
}
// Generates a tls.Config object for a client from the given files.
func (info TLSInfo) ClientConfig() (*tls.Config, error) {
var cfg tls.Config
if info.KeyFile == "" || info.CertFile == "" {
return &cfg, nil
}
tlsCert, err := tls.LoadX509KeyPair(info.CertFile, info.KeyFile)
if err != nil {
return nil, err
}
cfg.Certificates = []tls.Certificate{tlsCert}
if info.CAFile != "" {
cp, err := newCertPool(info.CAFile)
if err != nil {
return nil, err
}
cfg.RootCAs = cp
}
return &cfg, nil
}
// newCertPool creates x509 certPool with provided CA file
func newCertPool(CAFile string) (*x509.CertPool, error) {
certPool := x509.NewCertPool()
pemByte, err := ioutil.ReadFile(CAFile)
if err != nil {
return nil, err
}
for {
var block *pem.Block
block, pemByte = pem.Decode(pemByte)
if block == nil {
return certPool, nil
}
cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
return nil, err
}
certPool.AddCert(cert)
}
}

View File

@ -1,133 +0,0 @@
/*
Copyright 2014 CoreOS Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package etcdserver
import (
"errors"
"fmt"
"log"
"net/url"
"path"
"strings"
"time"
etcdErr "github.com/coreos/etcd/error"
"github.com/coreos/etcd/third_party/github.com/coreos/go-etcd/etcd"
)
const (
stateKey = "_state"
startedState = "started"
defaultTTL = 604800 // One week TTL
)
type discoverer struct {
client *etcd.Client
name string
addr string
prefix string
}
func newDiscoverer(u *url.URL, name, raftPubAddr string) *discoverer {
d := &discoverer{name: name, addr: raftPubAddr}
// prefix is prepended to all keys for this discovery
d.prefix = strings.TrimPrefix(u.Path, "/v2/keys/")
// keep the old path in case we need to set the KeyPrefix below
oldPath := u.Path
u.Path = ""
// Connect to a scheme://host not a full URL with path
d.client = etcd.NewClient([]string{u.String()})
if !strings.HasPrefix(oldPath, "/v2/keys") {
d.client.SetKeyPrefix("")
}
return d
}
func (d *discoverer) discover() ([]string, error) {
log.Printf("discoverer name=%s target=\"%q\" prefix=%s\n", d.name, d.client.GetCluster(), d.prefix)
if _, err := d.client.Set(path.Join(d.prefix, d.name), d.addr, defaultTTL); err != nil {
return nil, fmt.Errorf("discovery service error: %v", err)
}
// Attempt to take the leadership role, if there is no error we are it!
resp, err := d.client.Create(path.Join(d.prefix, stateKey), startedState, 0)
// Bail out on unexpected errors
if err != nil {
if clientErr, ok := err.(*etcd.EtcdError); !ok || clientErr.ErrorCode != etcdErr.EcodeNodeExist {
return nil, fmt.Errorf("discovery service error: %v", err)
}
}
// If we got a response then the CAS was successful, we are leader
if resp != nil && resp.Node.Value == startedState {
// We are the leader, we have no peers
return nil, nil
}
// Fall through to finding the other discovery peers
return d.findPeers()
}
func (d *discoverer) findPeers() (peers []string, err error) {
resp, err := d.client.Get(path.Join(d.prefix), false, true)
if err != nil {
return nil, fmt.Errorf("discovery service error: %v", err)
}
node := resp.Node
if node == nil {
return nil, fmt.Errorf("%s key doesn't exist.", d.prefix)
}
for _, n := range node.Nodes {
// Skip our own entry in the list, there is no point
if strings.HasSuffix(n.Key, "/"+d.name) {
continue
}
peers = append(peers, n.Value)
}
if len(peers) == 0 {
return nil, errors.New("Discovery found an initialized cluster but no reachable peers are registered.")
}
return
}
func (d *discoverer) heartbeat(stopc <-chan struct{}) {
// In case of errors we should attempt to heartbeat fairly frequently
heartbeatInterval := defaultTTL / 8
ticker := time.NewTicker(time.Second * time.Duration(heartbeatInterval))
defer ticker.Stop()
for {
if _, err := d.client.Set(path.Join(d.prefix, d.name), d.addr, defaultTTL); err != nil {
log.Println("discoverer heartbeatErr=\"%v\"", err)
}
select {
case <-ticker.C:
case <-stopc:
return
}
}
}

View File

@ -1,220 +0,0 @@
/*
Copyright 2014 CoreOS Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package etcdserver
import (
"crypto/tls"
"fmt"
"log"
"net"
"net/http"
"net/url"
"os"
"time"
"github.com/coreos/etcd/conf"
)
const (
participantMode int64 = iota
standbyMode
stopMode
)
type Server struct {
cfg *conf.Config
name string
pubAddr string
raftPubAddr string
tickDuration time.Duration
mode atomicInt
p *participant
s *standby
client *v2client
peerHub *peerHub
exited chan error
stopNotifyc chan struct{}
log *log.Logger
http.Handler
}
func New(c *conf.Config) (*Server, error) {
if err := c.Sanitize(); err != nil {
log.Fatalf("server.new sanitizeErr=\"%v\"\n", err)
}
tc := &tls.Config{
InsecureSkipVerify: true,
}
var err error
if c.PeerTLSInfo().Scheme() == "https" {
tc, err = c.PeerTLSInfo().ClientConfig()
if err != nil {
log.Fatalf("server.new ClientConfigErr=\"%v\"\n", err)
}
}
tr := new(http.Transport)
tr.TLSClientConfig = tc
tr.Dial = (&net.Dialer{Timeout: 200 * time.Millisecond}).Dial
tr.TLSHandshakeTimeout = 10 * time.Second
tr.ResponseHeaderTimeout = defaultTickDuration * defaultHeartbeat
client := &http.Client{Transport: tr}
s := &Server{
cfg: c,
name: c.Name,
pubAddr: c.Addr,
raftPubAddr: c.Peer.Addr,
tickDuration: defaultTickDuration,
mode: atomicInt(stopMode),
client: newClient(tc),
exited: make(chan error, 1),
stopNotifyc: make(chan struct{}),
}
followersStats := NewRaftFollowersStats(s.name)
s.peerHub = newPeerHub(client, followersStats)
m := http.NewServeMux()
m.HandleFunc("/", s.requestHandler)
m.HandleFunc("/version", versionHandler)
s.Handler = m
log.Printf("name=%s server.new raftPubAddr=%s\n", s.name, s.raftPubAddr)
if err = os.MkdirAll(s.cfg.DataDir, 0700); err != nil {
if !os.IsExist(err) {
return nil, err
}
}
return s, nil
}
func (s *Server) SetTick(tick time.Duration) {
s.tickDuration = tick
log.Printf("name=%s server.setTick tick=%q\n", s.name, s.tickDuration)
}
// Stop stops the server elegently.
func (s *Server) Stop() error {
s.mode.Set(stopMode)
close(s.stopNotifyc)
err := <-s.exited
s.client.CloseConnections()
log.Printf("name=%s server.stop\n", s.name)
return err
}
func (s *Server) requestHandler(w http.ResponseWriter, r *http.Request) {
switch s.mode.Get() {
case participantMode:
s.p.ServeHTTP(w, r)
case standbyMode:
s.s.ServeHTTP(w, r)
default:
http.NotFound(w, r)
}
}
func (s *Server) RaftHandler() http.Handler {
return http.HandlerFunc(s.ServeRaftHTTP)
}
func (s *Server) ServeRaftHTTP(w http.ResponseWriter, r *http.Request) {
switch s.mode.Get() {
case participantMode:
s.p.raftHandler().ServeHTTP(w, r)
default:
http.NotFound(w, r)
}
}
func (s *Server) Run() error {
var d *discoverer
var seeds []string
var exit error
defer func() { s.exited <- exit }()
durl := s.cfg.Discovery
if durl != "" {
u, err := url.Parse(durl)
if err != nil {
exit = err
return fmt.Errorf("bad discovery URL error: %v", err)
}
d = newDiscoverer(u, s.name, s.raftPubAddr)
if seeds, err = d.discover(); err != nil {
exit = err
return err
}
log.Printf("name=%s server.run source=-discovery seeds=%q", s.name, seeds)
} else {
for _, p := range s.cfg.Peers {
u, err := url.Parse(p)
if err != nil {
log.Printf("name=%s server.run err=%q", s.name, err)
continue
}
if u.Scheme == "" {
u.Scheme = s.cfg.PeerTLSInfo().Scheme()
}
seeds = append(seeds, u.String())
}
log.Printf("name=%s server.run source=-peers seeds=%q", s.name, seeds)
}
s.peerHub.setSeeds(seeds)
next := participantMode
for {
switch next {
case participantMode:
p, err := newParticipant(s.cfg, s.client, s.peerHub, s.tickDuration)
if err != nil {
log.Printf("name=%s server.run newParicipanteErr=\"%v\"\n", s.name, err)
exit = err
return err
}
s.p = p
s.mode.Set(participantMode)
log.Printf("name=%s server.run mode=participantMode\n", s.name)
dStopc := make(chan struct{})
if d != nil {
go d.heartbeat(dStopc)
}
s.p.run(s.stopNotifyc)
if d != nil {
close(dStopc)
}
next = standbyMode
case standbyMode:
s.s = newStandby(s.client, s.peerHub)
s.mode.Set(standbyMode)
log.Printf("name=%s server.run mode=standbyMode\n", s.name)
s.s.run(s.stopNotifyc)
next = participantMode
default:
panic("unsupport mode")
}
if s.mode.Get() == stopMode {
return nil
}
}
}

View File

@ -1,207 +0,0 @@
/*
Copyright 2014 CoreOS Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package etcdserver
import (
"fmt"
"math/rand"
"net/url"
"reflect"
"testing"
"time"
"github.com/coreos/etcd/conf"
"github.com/coreos/etcd/third_party/github.com/coreos/go-etcd/etcd"
)
func TestKillLeader(t *testing.T) {
defer afterTest(t)
tests := []int{3, 5, 9}
for i, tt := range tests {
cl := testCluster{Size: tt}
cl.Start()
for j := 0; j < tt; j++ {
lead, _ := cl.Leader()
cl.Node(lead).Stop()
// wait for leader election timeout
time.Sleep(cl.Node(0).e.tickDuration * defaultElection * 2)
if g, _ := cl.Leader(); g == lead {
t.Errorf("#%d.%d: lead = %d, want not %d", i, j, g, lead)
}
cl.Node(lead).Start()
cl.Node(lead).WaitMode(participantMode)
}
cl.Destroy()
}
}
func TestKillRandom(t *testing.T) {
defer afterTest(t)
tests := []int{3, 5, 9}
for _, tt := range tests {
cl := testCluster{Size: tt}
cl.Start()
for j := 0; j < tt; j++ {
// we cannot kill the majority
// wait for the majority
cl.Leader()
toKill := make(map[int64]struct{})
for len(toKill) != tt/2-1 {
toKill[rand.Int63n(int64(tt))] = struct{}{}
}
for k := range toKill {
cl.Node(int(k)).Stop()
}
// wait for leader election timeout
time.Sleep(cl.Node(0).e.tickDuration * defaultElection * 2)
cl.Leader()
for k := range toKill {
cl.Node(int(k)).Start()
cl.Node(int(k)).WaitMode(participantMode)
}
}
cl.Destroy()
}
}
func TestJoinThroughFollower(t *testing.T) {
defer afterTest(t)
tests := []int{3, 5, 7}
for _, tt := range tests {
bt := &testServer{}
bt.Start()
cl := testCluster{nodes: []*testServer{bt}}
seed := bt.URL
for i := 1; i < tt; i++ {
c := newTestConfig()
c.Name = fmt.Sprint(i)
c.Peers = []string{seed}
ts := &testServer{Config: c}
ts.Start()
ts.WaitMode(participantMode)
cl.nodes = append(cl.nodes, ts)
cl.Leader()
seed = ts.URL
}
cl.Destroy()
}
}
func TestJoinWithoutHTTPScheme(t *testing.T) {
bt := &testServer{}
bt.Start()
cl := testCluster{nodes: []*testServer{bt}}
seed := bt.URL
u, err := url.Parse(seed)
if err != nil {
t.Fatal(err)
}
// remove HTTP scheme
seed = u.Host + u.Path
for i := 1; i < 3; i++ {
c := newTestConfig()
c.Name = "server-" + fmt.Sprint(i)
c.Peers = []string{seed}
ts := &testServer{Config: c}
ts.Start()
ts.WaitMode(participantMode)
cl.nodes = append(cl.nodes, ts)
cl.Leader()
}
cl.Destroy()
}
func TestClusterConfigReload(t *testing.T) {
defer afterTest(t)
cl := &testCluster{Size: 5}
cl.Start()
defer cl.Destroy()
lead, _ := cl.Leader()
cc := conf.NewClusterConfig()
cc.ActiveSize = 15
cc.RemoveDelay = 60
if err := cl.Participant(lead).setClusterConfig(cc); err != nil {
t.Fatalf("setClusterConfig err = %v", err)
}
cl.Stop()
cl.Restart()
lead, _ = cl.Leader()
// wait for msgAppResp to commit all entries
time.Sleep(2 * defaultHeartbeat * cl.Participant(0).tickDuration)
if g := cl.Participant(lead).clusterConfig(); !reflect.DeepEqual(g, cc) {
t.Errorf("clusterConfig = %+v, want %+v", g, cc)
}
}
func TestFiveNodeKillOneAndRecover(t *testing.T) {
defer afterTest(t)
cl := testCluster{Size: 5}
cl.Start()
for n := 0; n < 5; n++ {
i := rand.Int() % 5
cl.Node(i).Stop()
cl.Leader()
cl.Node(i).Start()
cl.Node(i).WaitMode(participantMode)
cl.Leader()
}
cl.Destroy()
}
func TestFiveNodeKillAllAndRecover(t *testing.T) {
defer afterTest(t)
cl := testCluster{Size: 5}
cl.Start()
defer cl.Destroy()
cl.Leader()
c := etcd.NewClient([]string{cl.URL(0)})
for i := 0; i < 10; i++ {
if _, err := c.Set("foo", "bar", 0); err != nil {
panic(err)
}
}
cl.Stop()
cl.Restart()
cl.Leader()
res, err := c.Set("foo", "bar", 0)
if err != nil {
t.Fatalf("set err after recovery: %v", err)
}
if g := res.Node.ModifiedIndex; g != 16 {
t.Errorf("modifiedIndex = %d, want %d", g, 16)
}
}
// TestModeSwitch tests switch mode between standby and peer.
func TestModeSwitch(t *testing.T) {
t.Skip("not implemented")
}

View File

@ -1,168 +0,0 @@
/*
Copyright 2014 CoreOS Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package etcdserver
import (
"fmt"
"net/http"
"net/http/httptest"
"net/url"
"strings"
"sync"
"testing"
"time"
)
const (
bootstrapName = "BEEF"
)
type garbageHandler struct {
t *testing.T
success bool
sync.Mutex
}
func (g *garbageHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Hello, client")
wp := fmt.Sprint("/v2/keys/_etcd/registry/1/", bootstrapName)
if gp := r.URL.String(); gp != wp {
g.t.Fatalf("url = %s, want %s", gp, wp)
}
g.Lock()
defer g.Unlock()
g.success = true
}
func TestBadDiscoveryService(t *testing.T) {
defer afterTest(t)
g := garbageHandler{t: t}
httpts := httptest.NewServer(&g)
defer httpts.Close()
c := newTestConfig()
c.Name = bootstrapName
c.Discovery = httpts.URL + "/v2/keys/_etcd/registry/1"
ts := testServer{Config: c}
ts.Start()
err := ts.Destroy()
w := `discovery service error`
if err == nil || !strings.HasPrefix(err.Error(), w) {
t.Errorf("err = %v, want %s prefix", err, w)
}
g.Lock()
defer g.Unlock()
if !g.success {
t.Fatal("Discovery server never called")
}
}
func TestBadDiscoveryServiceWithAdvisedPeers(t *testing.T) {
defer afterTest(t)
g := garbageHandler{t: t}
httpts := httptest.NewServer(&g)
defer httpts.Close()
c := newTestConfig()
c.Name = bootstrapName
c.Discovery = httpts.URL + "/v2/keys/_etcd/registry/1"
c.Peers = []string{"a peer"}
ts := testServer{Config: c}
ts.Start()
err := ts.Destroy()
w := `discovery service error`
if err == nil || !strings.HasPrefix(err.Error(), w) {
t.Errorf("err = %v, want %s prefix", err, w)
}
}
func TestBootstrapByEmptyPeers(t *testing.T) {
defer afterTest(t)
ts := testServer{}
ts.Start()
defer ts.Destroy()
ts.WaitMode(participantMode)
if ts.Participant().node.Leader() != ts.Participant().id {
t.Errorf("leader = %x, want %x", ts.Participant().node.Leader(), ts.Participant().id)
}
}
func TestBootstrapByDiscoveryService(t *testing.T) {
defer afterTest(t)
discoverService := testCluster{Size: 1}
discoverService.Start()
defer discoverService.Destroy()
c := newTestConfig()
c.Name = bootstrapName
c.Discovery = discoverService.URL(0) + "/v2/keys/_etcd/registry/1"
ts := testServer{Config: c}
ts.Start()
ts.WaitMode(participantMode)
err := ts.Destroy()
if err != nil {
t.Fatalf("server stop err = %v, want nil", err)
}
}
func TestRunByAdvisedPeers(t *testing.T) {
t.Skip("test covered by TestMultipleNodes")
}
func TestRunByDiscoveryService(t *testing.T) {
ds := testCluster{Size: 1}
ds.Start()
defer ds.Destroy()
tc := NewTestClient()
v := url.Values{}
v.Set("value", "started")
resp, _ := tc.PutForm(fmt.Sprintf("%s%s", ds.URL(0), "/v2/keys/_etcd/registry/1/_state"), v)
if g := resp.StatusCode; g != http.StatusCreated {
t.Fatalf("put status = %d, want %d", g, http.StatusCreated)
}
resp.Body.Close()
v.Set("value", ds.URL(0))
resp, _ = tc.PutForm(fmt.Sprintf("%s%s%d", ds.URL(0), "/v2/keys/_etcd/registry/1/", ds.Participant(0).id), v)
if g := resp.StatusCode; g != http.StatusCreated {
t.Fatalf("put status = %d, want %d", g, http.StatusCreated)
}
resp.Body.Close()
c := newTestConfig()
c.Name = bootstrapName
c.Discovery = ds.URL(0) + "/v2/keys/_etcd/registry/1"
ts := &testServer{Config: c}
ds.Add(ts)
// wait for the leader to do a heartbeat
// it will update the lead field of the follower
time.Sleep(ds.Node(0).e.tickDuration * defaultHeartbeat * 2)
w := ds.Participant(0).id
if g := ts.Lead().lead; g != w {
t.Errorf("leader = %d, want %d", g, w)
}
}
func TestRunByDataDir(t *testing.T) {
t.Skip("test covered by TestSingleNodeRecovery")
}

View File

@ -1,656 +0,0 @@
/*
Copyright 2014 CoreOS Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package etcdserver
import (
"fmt"
"io/ioutil"
"net"
"net/http"
"net/http/httptest"
"net/url"
"os"
"path"
"reflect"
"strings"
"testing"
"time"
"github.com/coreos/etcd/conf"
"github.com/coreos/etcd/store"
)
func TestMultipleNodes(t *testing.T) {
defer afterTest(t)
tests := []int{1, 3, 5, 9, 11}
for _, tt := range tests {
c := &testCluster{Size: tt}
c.Start()
c.Destroy()
}
}
func TestMultipleTLSNodes(t *testing.T) {
defer afterTest(t)
tests := []int{1, 3, 5}
for _, tt := range tests {
c := &testCluster{Size: tt, TLS: true}
c.Start()
c.Destroy()
}
}
func TestV2Redirect(t *testing.T) {
defer afterTest(t)
c := &testCluster{Size: 3}
c.Start()
defer c.Destroy()
u := c.URL(1)
ru := fmt.Sprintf("%s%s", c.URL(0), "/v2/keys/foo")
tc := NewTestClient()
v := url.Values{}
v.Set("value", "XXX")
resp, _ := tc.PutForm(fmt.Sprintf("%s%s", u, "/v2/keys/foo"), v)
if resp.StatusCode != http.StatusTemporaryRedirect {
t.Errorf("status = %d, want %d", resp.StatusCode, http.StatusTemporaryRedirect)
}
location, err := resp.Location()
if err != nil {
t.Errorf("want err = %, want nil", err)
}
if location.String() != ru {
t.Errorf("location = %v, want %v", location.String(), ru)
}
resp.Body.Close()
}
func TestRemove(t *testing.T) {
defer afterTest(t)
tests := []int{3, 4, 5, 6}
for aa := 0; aa < 1; aa++ {
for k, tt := range tests {
cl := testCluster{Size: tt}
cl.Start()
lead, _ := cl.Leader()
config := conf.NewClusterConfig()
config.ActiveSize = 0
if err := cl.Participant(lead).setClusterConfig(config); err != nil {
t.Fatalf("#%d: setClusterConfig err = %v", k, err)
}
// we don't remove the machine from 2-node cluster because it is
// not 100 percent safe in our raft.
// TODO(yichengq): improve it later.
for i := 0; i < tt-2; i++ {
id := cl.Id(i)
for {
n := cl.Node(i)
if n.e.mode.Get() == standbyMode {
break
}
err := n.Participant().remove(id)
if err == nil {
break
}
switch err {
case tmpErr:
time.Sleep(defaultElection * 5 * time.Millisecond)
case raftStopErr, stopErr:
default:
t.Fatal(err)
}
}
cl.Node(i).WaitMode(standbyMode)
}
cl.Destroy()
}
}
}
// TODO(yicheng) Add test for becoming standby
// maxSize -> standby
// auto-demote -> standby
// remove -> standby
func TestReleaseVersion(t *testing.T) {
defer afterTest(t)
cl := testCluster{Size: 1}
cl.Start()
defer cl.Destroy()
resp, err := http.Get(cl.URL(0) + "/version")
if err != nil {
t.Fatal(err)
}
defer resp.Body.Close()
g, err := ioutil.ReadAll(resp.Body)
if err != nil {
t.Error(err)
}
gs := string(g)
w := fmt.Sprintf("etcd %s", releaseVersion)
if gs != w {
t.Errorf("version = %v, want %v", gs, w)
}
}
func TestVersionCheck(t *testing.T) {
defer afterTest(t)
cl := testCluster{Size: 1}
cl.Start()
defer cl.Destroy()
u := cl.URL(0)
currentVersion := 2
tests := []struct {
version int
wStatus int
}{
{currentVersion - 1, http.StatusForbidden},
{currentVersion, http.StatusOK},
{currentVersion + 1, http.StatusForbidden},
}
for i, tt := range tests {
resp, err := http.Get(fmt.Sprintf("%s/raft/version/%d/check", u, tt.version))
if err != nil {
t.Fatal(err)
}
resp.Body.Close()
if resp.StatusCode != tt.wStatus {
t.Fatal("#%d: status = %d, want %d", i, resp.StatusCode, tt.wStatus)
}
}
}
func TestSingleNodeRecovery(t *testing.T) {
defer afterTest(t)
c := newTestConfig()
ts := testServer{Config: c}
ts.Start()
defer ts.Destroy()
ts.WaitMode(participantMode)
key := "/foo"
ev, err := ts.Participant().Set(key, false, "bar", time.Now().Add(time.Second*100))
if err != nil {
t.Fatal(err)
}
ts.Stop()
ts = testServer{Config: c}
ts.Start()
ts.WaitMode(participantMode)
w, err := ts.Participant().Store.Watch(key, false, false, ev.Index())
if err != nil {
t.Fatal(err)
}
// give testing server time to load the previous WAL file
select {
case <-w.EventChan:
case <-time.After(time.Second):
t.Fatal("watch timeout")
}
}
func TestTakingSnapshot(t *testing.T) {
defer afterTest(t)
cl := testCluster{Size: 1}
cl.Start()
defer cl.Destroy()
// TODO(xiangli): tunable compact; reduce testing time
for i := 0; i < defaultCompact; i++ {
cl.Participant(0).Set("/foo", false, "bar", store.Permanent)
}
snap := cl.Participant(0).node.GetSnap()
if snap.Index != int64(defaultCompact) {
t.Errorf("snap.Index = %d, want %d", snap.Index, defaultCompact)
}
}
func TestRestoreSnapshotFromLeader(t *testing.T) {
defer afterTest(t)
cl := testCluster{Size: 1}
cl.Start()
defer cl.Destroy()
// let leader do snapshot
for i := 0; i < defaultCompact; i++ {
cl.Participant(0).Set(fmt.Sprint("/foo", i), false, fmt.Sprint("bar", i), store.Permanent)
}
// create one to join the cluster
c := newTestConfig()
c.Name = "1"
c.Peers = []string{cl.URL(0)}
ts := &testServer{Config: c}
cl.Add(ts)
// check new proposal could be submitted
if _, err := cl.Participant(0).Set("/foo", false, "bar", store.Permanent); err != nil {
t.Fatal(err)
}
// check store is recovered
for i := 0; i < defaultCompact; i++ {
ev, err := ts.Participant().Store.Get(fmt.Sprint("/foo", i), false, false)
if err != nil {
t.Errorf("get err = %v", err)
continue
}
w := fmt.Sprint("bar", i)
if g := *ev.Node.Value; g != w {
t.Errorf("value = %v, want %v", g, w)
}
}
// check new proposal could be committed in the new machine
wch, err := ts.Participant().Watch("/foo", false, false, uint64(defaultCompact))
if err != nil {
t.Errorf("watch err = %v", err)
}
<-wch.EventChan
// check node map of two machines are the same
g := ts.Participant().node.Nodes()
w := cl.Participant(0).node.Nodes()
if !reflect.DeepEqual(g, w) {
t.Errorf("nodes = %v, want %v", g, w)
}
}
func TestSaveSnapshot(t *testing.T) {
defer afterTest(t)
cl := testCluster{Size: 1}
cl.Start()
defer cl.Destroy()
n := cl.Node(0)
// TODO(xiangli): tunable compact; reduce testing time
for i := 0; i < defaultCompact; i++ {
n.Participant().Set("/foo", false, "bar", store.Permanent)
}
snapname := fmt.Sprintf("%016x-%016x-%016x.snap", n.Participant().clusterId, 1, defaultCompact)
snappath := path.Join(n.Config.DataDir, "snap", snapname)
if _, err := os.Stat(snappath); err != nil {
t.Errorf("err = %v, want nil", err)
}
walname := fmt.Sprintf("%016x-%016x.wal", 1, defaultCompact)
walpath := path.Join(n.Config.DataDir, "wal", walname)
if _, err := os.Stat(walpath); err != nil {
t.Errorf("err = %v, want nil", err)
}
}
func TestRestoreSnapshotFromDisk(t *testing.T) {
defer afterTest(t)
tests := []int{1, 3, 5}
// TODO(xiangli): tunable compact; reduce testing time
oldDefaultCompact := defaultCompact
defaultCompact = 10
defer func() { defaultCompact = oldDefaultCompact }()
for _, tt := range tests {
cl := testCluster{Size: tt}
cl.Start()
defer cl.Destroy()
lead, _ := cl.Leader()
for i := 0; i < defaultCompact+10; i++ {
cl.Participant(lead).Set(fmt.Sprint("/foo", i), false, fmt.Sprint("bar", i), store.Permanent)
}
cl.Stop()
cl.Restart()
lead, _ = cl.Leader()
// check store is recovered
for i := 0; i < defaultCompact+10; i++ {
ev, err := cl.Participant(lead).Store.Get(fmt.Sprint("/foo", i), false, false)
if err != nil {
t.Errorf("get err = %v", err)
continue
}
w := fmt.Sprint("bar", i)
if g := *ev.Node.Value; g != w {
t.Errorf("value = %v, want %v", g, w)
}
}
// check new proposal could be submitted
if _, err := cl.Participant(lead).Set("/foo", false, "bar", store.Permanent); err != nil {
t.Fatal(err)
}
}
}
type testCluster struct {
Size int
TLS bool
nodes []*testServer
}
func (c *testCluster) Start() {
if c.Size <= 0 {
panic("cluster size <= 0")
}
nodes := make([]*testServer, c.Size)
c.nodes = nodes
cfg := newTestConfig()
cfg.Name = "testServer-0"
nodes[0] = &testServer{Config: cfg, TLS: c.TLS}
nodes[0].Start()
nodes[0].WaitMode(participantMode)
seed := nodes[0].URL
for i := 1; i < c.Size; i++ {
cfg := newTestConfig()
cfg.Name = "testServer-" + fmt.Sprint(i)
cfg.Peers = []string{seed}
s := &testServer{Config: cfg, TLS: c.TLS}
s.Start()
nodes[i] = s
// Wait for the previous configuration change to be committed
// or this configuration request might be dropped.
// Or it could be a slow join because it needs to retry.
// TODO: this might not be true if we add param for retry interval.
s.WaitMode(participantMode)
w, err := s.Participant().Watch(v2machineKVPrefix, true, false, uint64(i))
if err != nil {
panic(err)
}
<-w.EventChan
}
c.wait()
}
func (c *testCluster) wait() {
size := c.Size
for i := 0; i < size; i++ {
for k := 0; k < size; k++ {
s := c.Node(i)
wp := v2machineKVPrefix + fmt.Sprintf("/%d", c.Id(k))
w, err := s.Participant().Watch(wp, false, false, 1)
if err != nil {
panic(err)
}
<-w.EventChan
}
}
clusterId := c.Participant(0).node.ClusterId()
for i := 0; i < size; i++ {
if g := c.Participant(i).node.ClusterId(); g != clusterId {
panic(fmt.Sprintf("#%d: clusterId = %x, want %x", i, g, clusterId))
}
}
}
func (c *testCluster) Add(s *testServer) {
lead, _ := c.Leader()
// wait for the node to join the cluster
// TODO(yichengq): remove this when we get rid of all timeouts
wch, err := c.Participant(int(lead)).Watch(v2machineKVPrefix, true, false, 0)
if err != nil {
panic(err)
}
s.Start()
<-wch.EventChan
c.Size++
c.nodes = append(c.nodes, s)
}
func (c *testCluster) Node(i int) *testServer {
return c.nodes[i]
}
func (c *testCluster) Participant(i int) *participant {
return c.Node(i).Participant()
}
func (c *testCluster) Standby(i int) *standby {
return c.Node(i).Standby()
}
func (c *testCluster) URL(i int) string {
return c.nodes[i].h.URL
}
func (c *testCluster) Id(i int) int64 {
return c.Participant(i).id
}
func (c *testCluster) Restart() {
for _, s := range c.nodes {
s.Start()
}
}
func (c *testCluster) Stop() {
for _, s := range c.nodes {
s.Stop()
}
}
func (c *testCluster) Destroy() {
for _, s := range c.nodes {
s.Destroy()
}
}
// Leader returns the index of leader in the cluster and its leader term.
func (c *testCluster) Leader() (leadIdx int, term int64) {
ids := make(map[int64]int)
for {
ls := make([]leadterm, 0, c.Size)
for i := range c.nodes {
switch c.Node(i).e.mode.Get() {
case participantMode:
ls = append(ls, c.Node(i).Lead())
ids[c.Id(i)] = i
case standbyMode:
//TODO(xiangli) add standby support
case stopMode:
}
}
if isSameLead(ls) {
return ids[ls[0].lead], ls[0].term
}
time.Sleep(c.Node(0).e.tickDuration * defaultElection)
}
}
type leadterm struct {
lead int64
term int64
}
func isSameLead(ls []leadterm) bool {
m := make(map[leadterm]int)
for i := range ls {
m[ls[i]] = m[ls[i]] + 1
}
if len(m) == 1 {
if ls[0].lead == -1 {
return false
}
return true
}
// todo(xiangli): printout the current cluster status for debugging....
return false
}
type testServer struct {
Config *conf.Config
TLS bool
// base URL of form http://ipaddr:port with no trailing slash
URL string
e *Server
h *httptest.Server
}
func (s *testServer) Start() {
if s.Config == nil {
s.Config = newTestConfig()
}
c := s.Config
if !strings.HasPrefix(c.DataDir, os.TempDir()) {
panic("dataDir may pollute file system")
}
if c.Peer.CAFile != "" || c.Peer.CertFile != "" || c.Peer.KeyFile != "" {
panic("use TLS field instead")
}
nc := *c
e, err := New(&nc)
if err != nil {
panic(err)
}
s.e = e
tick := time.Duration(c.Peer.HeartbeatInterval) * time.Millisecond
e.SetTick(tick)
m := http.NewServeMux()
m.Handle("/", e)
m.Handle("/raft", e.RaftHandler())
m.Handle("/raft/", e.RaftHandler())
m.Handle("/v2/admin/", e.RaftHandler())
addr := c.Addr
if s.URL != "" {
addr = urlHost(s.URL)
}
s.h = startServingAddr(addr, m, s.TLS)
s.URL = s.h.URL
e.pubAddr = s.URL
e.raftPubAddr = s.URL
e.cfg.Addr = s.URL
e.cfg.Peer.Addr = s.URL
go e.Run()
}
func (s *testServer) WaitMode(mode int64) {
for i := 0; i < 30; i++ {
if s.e.mode.Get() == mode {
return
}
time.Sleep(time.Millisecond)
}
panic("waitMode should never take more than 30ms.")
}
func (s *testServer) Participant() *participant {
if s.e.mode.Get() != participantMode {
return nil
}
return s.e.p
}
func (s *testServer) Standby() *standby {
return s.e.s
}
func (s *testServer) Lead() leadterm {
return leadterm{s.Participant().node.Leader(), s.Participant().node.Term()}
}
func (s *testServer) Stop() error {
err := s.e.Stop()
s.h.Close()
return err
}
func (s *testServer) Destroy() error {
err := s.Stop()
if err := os.RemoveAll(s.Config.DataDir); err != nil {
panic(err)
}
return err
}
func startServingAddr(addr string, h http.Handler, tls bool) *httptest.Server {
var l net.Listener
var err error
for i := 0; i < 4; i++ {
l, err = net.Listen("tcp", addr)
if err == nil {
break
}
if !strings.Contains(err.Error(), "address already in use") {
panic(err)
}
time.Sleep(500 * time.Millisecond)
}
if l == nil {
panic("cannot listen on " + addr)
}
hs := &httptest.Server{
Listener: l,
Config: &http.Server{Handler: h},
}
if tls {
hs.StartTLS()
} else {
hs.Start()
}
return hs
}
func newTestConfig() *conf.Config {
c := conf.New()
c.Addr = "127.0.0.1:0"
c.Peer.Addr = "127.0.0.1:0"
c.Peer.HeartbeatInterval = 5
c.Peer.ElectionTimeout = 25
c.RetryInterval = 1 / 10.0
dataDir, err := ioutil.TempDir(os.TempDir(), "etcd")
if err != nil {
panic(err)
}
c.DataDir = dataDir
return c
}
func urlHost(urlStr string) string {
u, err := url.Parse(urlStr)
if err != nil {
panic(err)
}
return u.Host
}

View File

@ -17,11 +17,12 @@ import (
crand "crypto/rand" crand "crypto/rand"
"math/rand" "math/rand"
"code.google.com/p/go.net/context" "code.google.com/p/go.net/context"
"github.com/coreos/etcd/elog" "github.com/coreos/etcd/elog"
etcderrors "github.com/coreos/etcd/error" etcderrors "github.com/coreos/etcd/error"
etcdserver "github.com/coreos/etcd/etcdserver2" "github.com/coreos/etcd/etcdserver"
"github.com/coreos/etcd/etcdserver2/etcdserverpb" "github.com/coreos/etcd/etcdserver/etcdserverpb"
"github.com/coreos/etcd/raft/raftpb" "github.com/coreos/etcd/raft/raftpb"
"github.com/coreos/etcd/store" "github.com/coreos/etcd/store"
) )

View File

@ -8,9 +8,10 @@ import (
"reflect" "reflect"
"testing" "testing"
"time" "time"
"code.google.com/p/go.net/context" "code.google.com/p/go.net/context"
etcdserver "github.com/coreos/etcd/etcdserver2" "github.com/coreos/etcd/etcdserver"
"github.com/coreos/etcd/raft" "github.com/coreos/etcd/raft"
"github.com/coreos/etcd/raft/raftpb" "github.com/coreos/etcd/raft/raftpb"
"github.com/coreos/etcd/store" "github.com/coreos/etcd/store"

View File

@ -1,25 +0,0 @@
package etcdserver
import (
"time"
)
// packageStats represent the stats we need for a package.
// It has sending time and the size of the package.
type packageStats struct {
sendingTime time.Time
size int
}
// NewPackageStats creates a pacakgeStats and return the pointer to it.
func NewPackageStats(now time.Time, size int) *packageStats {
return &packageStats{
sendingTime: now,
size: size,
}
}
// Time return the sending time of the package.
func (ps *packageStats) Time() time.Time {
return ps.sendingTime
}

View File

@ -1,522 +0,0 @@
/*
Copyright 2014 CoreOS Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package etcdserver
import (
"encoding/json"
"fmt"
"log"
"math/rand"
"net/http"
"net/url"
"os"
"path"
"time"
"github.com/coreos/etcd/conf"
etcdErr "github.com/coreos/etcd/error"
"github.com/coreos/etcd/raft"
"github.com/coreos/etcd/snap"
"github.com/coreos/etcd/store"
"github.com/coreos/etcd/wal"
)
const (
defaultHeartbeat = 1
defaultElection = 5
maxBufferedProposal = 128
defaultTickDuration = time.Millisecond * 100
v2machineKVPrefix = "/_etcd/machines"
v2configKVPrefix = "/_etcd/config"
v2Prefix = "/v2/keys"
v2machinePrefix = "/v2/machines"
v2peersPrefix = "/v2/peers"
v2LeaderPrefix = "/v2/leader"
v2SelfStatsPrefix = "/v2/stats/self"
v2LeaderStatsPrefix = "/v2/stats/leader"
v2StoreStatsPrefix = "/v2/stats/store"
v2adminConfigPrefix = "/v2/admin/config"
v2adminMachinesPrefix = "/v2/admin/machines/"
)
var (
defaultCompact = 10000
tmpErr = fmt.Errorf("try again")
stopErr = fmt.Errorf("server is stopped")
raftStopErr = fmt.Errorf("raft is stopped")
)
type participant struct {
id int64
clusterId int64
cfg *conf.Config
pubAddr string
raftPubAddr string
tickDuration time.Duration
client *v2client
peerHub *peerHub
proposal chan v2Proposal
addNodeC chan raft.Config
removeNodeC chan raft.Config
node *v2Raft
store.Store
rh *raftHandler
w *wal.WAL
snapshotter *snap.Snapshotter
serverStats *raftServerStats
stopNotifyc chan struct{}
*http.ServeMux
}
func newParticipant(c *conf.Config, client *v2client, peerHub *peerHub, tickDuration time.Duration) (*participant, error) {
p := &participant{
clusterId: -1,
cfg: c,
tickDuration: tickDuration,
client: client,
peerHub: peerHub,
proposal: make(chan v2Proposal, maxBufferedProposal),
addNodeC: make(chan raft.Config, 1),
removeNodeC: make(chan raft.Config, 1),
node: &v2Raft{
result: make(map[wait]chan interface{}),
},
Store: store.New(),
serverStats: NewRaftServerStats(c.Name),
stopNotifyc: make(chan struct{}),
ServeMux: http.NewServeMux(),
}
p.rh = newRaftHandler(peerHub, p.Store.Version(), p.serverStats)
p.peerHub.setServerStats(p.serverStats)
snapDir := path.Join(p.cfg.DataDir, "snap")
if err := os.Mkdir(snapDir, 0700); err != nil {
if !os.IsExist(err) {
log.Printf("id=%x participant.new.mkdir err=%v", p.id, err)
return nil, err
}
}
p.snapshotter = snap.New(snapDir)
walDir := path.Join(p.cfg.DataDir, "wal")
if err := os.Mkdir(walDir, 0700); err != nil {
if !os.IsExist(err) {
log.Printf("id=%x participant.new.mkdir err=%v", p.id, err)
return nil, err
}
}
var w *wal.WAL
var err error
if !wal.Exist(walDir) {
p.id = genId()
p.pubAddr = c.Addr
p.raftPubAddr = c.Peer.Addr
if w, err = wal.Create(walDir); err != nil {
return nil, err
}
p.node.Node = raft.New(p.id, defaultHeartbeat, defaultElection)
info := p.node.Info()
if err = w.SaveInfo(&info); err != nil {
return nil, err
}
log.Printf("id=%x participant.new path=%s\n", p.id, walDir)
} else {
s, err := p.snapshotter.Load()
if err != nil && err != snap.ErrNoSnapshot {
log.Printf("participant.snapload err=%s\n", err)
return nil, err
}
var snapIndex int64
if s != nil {
if err := p.Recovery(s.Data); err != nil {
log.Printf("store.recover err=%v", err)
return nil, err
}
log.Printf("participant.store.recovered index=%d\n", s.Index)
for _, node := range s.Nodes {
pp := path.Join(v2machineKVPrefix, fmt.Sprint(node))
ev, err := p.Store.Get(pp, false, false)
if err != nil {
log.Printf("store.get err=%v", err)
return nil, err
}
q, err := url.ParseQuery(*ev.Node.Value)
if err != nil {
log.Printf("url.parse err=%v", err)
return nil, err
}
peer, err := p.peerHub.add(node, q["raft"][0])
if err != nil {
log.Printf("peerHub.add err=%v", err)
return nil, err
}
peer.participate()
}
snapIndex = s.Index
}
n, err := wal.Read(walDir, snapIndex)
if err != nil {
return nil, err
}
p.id = n.Id
p.node.Node = raft.Recover(n.Id, s, n.Ents, n.State, defaultHeartbeat, defaultElection)
p.apply(p.node.Next())
log.Printf("id=%x participant.load path=%s snapIndex=%d state=\"%+v\" len(ents)=%d", p.id, p.cfg.DataDir, snapIndex, n.State, len(n.Ents))
if w, err = wal.Open(walDir); err != nil {
return nil, err
}
}
p.w = w
p.Handle(v2Prefix+"/", handlerErr(p.serveValue))
p.Handle(v2machinePrefix, handlerErr(p.serveMachines))
p.Handle(v2peersPrefix, handlerErr(p.serveMachines))
p.Handle(v2LeaderPrefix, handlerErr(p.serveLeader))
p.Handle(v2SelfStatsPrefix, handlerErr(p.serveSelfStats))
p.Handle(v2LeaderStatsPrefix, handlerErr(p.serveLeaderStats))
p.Handle(v2StoreStatsPrefix, handlerErr(p.serveStoreStats))
p.rh.Handle(v2adminConfigPrefix, handlerErr(p.serveAdminConfig))
p.rh.Handle(v2adminMachinesPrefix, handlerErr(p.serveAdminMachines))
// TODO: remind to set application/json for /v2/stats endpoint
return p, nil
}
func (p *participant) run(stop chan struct{}) error {
defer p.cleanup()
if p.node.IsEmpty() {
seeds := p.peerHub.getSeeds()
if len(seeds) == 0 {
log.Printf("id=%x participant.run action=bootstrap\n", p.id)
p.node.Campaign()
p.node.InitCluster(genId())
p.node.Add(p.id, p.raftPubAddr, []byte(p.pubAddr))
p.apply(p.node.Next())
} else {
log.Printf("id=%x participant.run action=join seeds=\"%v\"\n", p.id, seeds)
if err := p.join(); err != nil {
log.Printf("id=%x participant.join err=%q", p.id, err)
return err
}
}
}
p.rh.start()
defer p.rh.stop()
node := p.node
recv := p.rh.recv
ticker := time.NewTicker(p.tickDuration)
defer ticker.Stop()
v2SyncTicker := time.NewTicker(time.Millisecond * 500)
defer v2SyncTicker.Stop()
var proposal chan v2Proposal
var addNodeC, removeNodeC chan raft.Config
for {
if node.HasLeader() {
proposal = p.proposal
addNodeC = p.addNodeC
removeNodeC = p.removeNodeC
} else {
proposal = nil
addNodeC = nil
removeNodeC = nil
}
select {
case p := <-proposal:
node.Propose(p)
case c := <-addNodeC:
node.UpdateConf(raft.AddNode, &c)
case c := <-removeNodeC:
node.UpdateConf(raft.RemoveNode, &c)
case msg := <-recv:
node.Step(*msg)
case <-ticker.C:
node.Tick()
case <-v2SyncTicker.C:
node.Sync()
case <-stop:
log.Printf("id=%x participant.stop\n", p.id)
return nil
}
if s := node.UnstableSnapshot(); !s.IsEmpty() {
if err := p.Recovery(s.Data); err != nil {
log.Printf("id=%x participant.recover err=%q", p.id, err)
return err
}
log.Printf("id=%x participant.recovered index=%d", p.id, s.Index)
}
p.apply(node.Next())
if err := p.save(node.UnstableEnts(), node.UnstableState()); err != nil {
return err
}
p.send(node.Msgs())
if node.IsRemoved() {
log.Printf("id=%x participant.end\n", p.id)
return nil
}
if p.node.EntsLen() > defaultCompact {
d, err := p.Save()
if err != nil {
log.Printf("id=%x participant.compact err=%q", p.id, err)
return err
}
p.node.Compact(d)
snap := p.node.GetSnap()
log.Printf("id=%x compacted index=%d", p.id, snap.Index)
if err := p.snapshotter.Save(&snap); err != nil {
log.Printf("id=%x snapshot.save err=%v", p.id, err)
return err
}
if err := p.w.Cut(p.node.Index()); err != nil {
log.Printf("id=%x wal.cut err=%v", p.id, err)
return err
}
info := p.node.Info()
if err = p.w.SaveInfo(&info); err != nil {
log.Printf("id=%x wal.saveInfo err=%v", p.id, err)
return err
}
}
}
}
func (p *participant) cleanup() {
p.w.Close()
close(p.stopNotifyc)
p.peerHub.stop()
}
func (p *participant) raftHandler() http.Handler {
return p.rh
}
func (p *participant) add(id int64, raftPubAddr string, pubAddr string) error {
log.Printf("id=%x participant.add nodeId=%x raftPubAddr=%s pubAddr=%s\n", p.id, id, raftPubAddr, pubAddr)
pp := path.Join(v2machineKVPrefix, fmt.Sprint(id))
_, err := p.Store.Get(pp, false, false)
if err == nil {
return nil
}
if v, ok := err.(*etcdErr.Error); !ok || v.ErrorCode != etcdErr.EcodeKeyNotFound {
log.Printf("id=%x participant.add getErr=\"%v\"\n", p.id, err)
return err
}
w, err := p.Watch(pp, true, false, 0)
if err != nil {
log.Printf("id=%x participant.add watchErr=\"%v\"\n", p.id, err)
return tmpErr
}
select {
case p.addNodeC <- raft.Config{NodeId: id, Addr: raftPubAddr, Context: []byte(pubAddr)}:
default:
w.Remove()
log.Printf("id=%x participant.add proposeErr=\"unable to send out addNode proposal\"\n", p.id)
return tmpErr
}
select {
case v := <-w.EventChan:
if v.Action == store.Set {
return nil
}
log.Printf("id=%x participant.add watchErr=\"unexpected action\" action=%s\n", p.id, v.Action)
return tmpErr
case <-time.After(6 * defaultHeartbeat * p.tickDuration):
w.Remove()
log.Printf("id=%x participant.add watchErr=timeout\n", p.id)
return tmpErr
case <-p.stopNotifyc:
return stopErr
}
}
func (p *participant) remove(id int64) error {
log.Printf("id=%x participant.remove nodeId=%x\n", p.id, id)
pp := path.Join(v2machineKVPrefix, fmt.Sprint(id))
v, err := p.Store.Get(pp, false, false)
if err != nil {
return nil
}
select {
case p.removeNodeC <- raft.Config{NodeId: id}:
default:
log.Printf("id=%x participant.remove proposeErr=\"unable to send out removeNode proposal\"\n", p.id)
return tmpErr
}
// TODO(xiangli): do not need to watch if the
// removal target is self
w, err := p.Watch(pp, true, false, v.Index()+1)
if err != nil {
log.Printf("id=%x participant.remove watchErr=\"%v\"\n", p.id, err)
return tmpErr
}
select {
case v := <-w.EventChan:
if v.Action == store.Delete {
return nil
}
log.Printf("id=%x participant.remove watchErr=\"unexpected action\" action=%s\n", p.id, v.Action)
return tmpErr
case <-time.After(6 * defaultHeartbeat * p.tickDuration):
w.Remove()
log.Printf("id=%x participant.remove watchErr=timeout\n", p.id)
return tmpErr
case <-p.stopNotifyc:
return stopErr
}
}
func (p *participant) apply(ents []raft.Entry) {
offset := p.node.Applied() - int64(len(ents)) + 1
for i, ent := range ents {
switch ent.Type {
// expose raft entry type
case raft.Normal:
if len(ent.Data) == 0 {
continue
}
p.v2apply(offset+int64(i), ent)
case raft.ClusterInit:
p.clusterId = p.node.ClusterId()
log.Printf("id=%x participant.cluster.setId clusterId=%x\n", p.id, p.clusterId)
case raft.AddNode:
cfg := new(raft.Config)
if err := json.Unmarshal(ent.Data, cfg); err != nil {
log.Printf("id=%x participant.cluster.addNode unmarshalErr=\"%v\"\n", p.id, err)
break
}
pp := path.Join(v2machineKVPrefix, fmt.Sprint(cfg.NodeId))
if ev, _ := p.Store.Get(pp, false, false); ev != nil {
log.Printf("id=%x participant.cluster.addNode err=existed value=%q", p.id, *ev.Node.Value)
break
}
peer, err := p.peerHub.add(cfg.NodeId, cfg.Addr)
if err != nil {
log.Printf("id=%x participant.cluster.addNode peerAddErr=\"%v\"\n", p.id, err)
break
}
peer.participate()
p.Store.Set(pp, false, fmt.Sprintf("raft=%v&etcd=%v", cfg.Addr, string(cfg.Context)), store.Permanent)
if p.id == cfg.NodeId {
p.raftPubAddr = cfg.Addr
p.pubAddr = string(cfg.Context)
}
log.Printf("id=%x participant.cluster.addNode nodeId=%x addr=%s context=%s\n", p.id, cfg.NodeId, cfg.Addr, cfg.Context)
case raft.RemoveNode:
cfg := new(raft.Config)
if err := json.Unmarshal(ent.Data, cfg); err != nil {
log.Printf("id=%x participant.cluster.removeNode unmarshalErr=\"%v\"\n", p.id, err)
break
}
peer, err := p.peerHub.peer(cfg.NodeId)
if err != nil {
log.Fatal("id=%x participant.apply getPeerErr=\"%v\"", p.id, err)
}
peer.idle()
pp := path.Join(v2machineKVPrefix, fmt.Sprint(cfg.NodeId))
p.Store.Delete(pp, false, false)
log.Printf("id=%x participant.cluster.removeNode nodeId=%x\n", p.id, cfg.NodeId)
default:
panic("unimplemented")
}
}
}
func (p *participant) save(ents []raft.Entry, state raft.State) error {
for _, ent := range ents {
if err := p.w.SaveEntry(&ent); err != nil {
log.Printf("id=%x participant.save saveEntryErr=%q", p.id, err)
return err
}
}
if !state.IsEmpty() {
if err := p.w.SaveState(&state); err != nil {
log.Printf("id=%x participant.save saveStateErr=%q", p.id, err)
return err
}
}
if err := p.w.Sync(); err != nil {
log.Printf("id=%x participant.save syncErr=%q", p.id, err)
return err
}
return nil
}
func (p *participant) send(msgs []raft.Message) {
for i := range msgs {
if err := p.peerHub.send(msgs[i]); err != nil {
log.Printf("id=%x participant.send err=\"%v\"\n", p.id, err)
}
}
}
func (p *participant) join() error {
info := &context{
MinVersion: store.MinVersion(),
MaxVersion: store.MaxVersion(),
ClientURL: p.pubAddr,
PeerURL: p.raftPubAddr,
}
max := p.cfg.MaxRetryAttempts
for attempt := 0; ; attempt++ {
for seed := range p.peerHub.getSeeds() {
if err := p.client.AddMachine(seed, fmt.Sprint(p.id), info); err == nil {
return nil
} else {
log.Printf("id=%x participant.join addMachineErr=\"%v\"\n", p.id, err)
}
}
if attempt == max {
return fmt.Errorf("etcd: cannot join cluster after %d attempts", max)
}
time.Sleep(time.Millisecond * time.Duration(p.cfg.RetryInterval*1000))
}
}
func genId() int64 {
r := rand.New(rand.NewSource(int64(time.Now().Nanosecond())))
return r.Int63()
}

View File

@ -1,151 +0,0 @@
/*
Copyright 2014 CoreOS Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package etcdserver
import (
"bytes"
"fmt"
"log"
"net/http"
"sync"
"sync/atomic"
"time"
)
const (
maxInflight = 4
)
const (
participantPeer = iota
idlePeer
stoppedPeer
)
type peer struct {
url string
queue chan []byte
status int
inflight atomicInt
c *http.Client
followerStats *raftFollowerStats
mu sync.RWMutex
wg sync.WaitGroup
}
func newPeer(url string, c *http.Client, followerStats *raftFollowerStats) *peer {
return &peer{
url: url,
status: idlePeer,
c: c,
followerStats: followerStats,
}
}
func (p *peer) participate() {
p.mu.Lock()
defer p.mu.Unlock()
p.queue = make(chan []byte)
p.status = participantPeer
for i := 0; i < maxInflight; i++ {
p.wg.Add(1)
go p.handle(p.queue)
}
}
func (p *peer) idle() {
p.mu.Lock()
defer p.mu.Unlock()
if p.status == participantPeer {
close(p.queue)
}
p.status = idlePeer
}
func (p *peer) stop() {
p.mu.Lock()
if p.status == participantPeer {
close(p.queue)
}
p.status = stoppedPeer
p.mu.Unlock()
p.wg.Wait()
}
func (p *peer) handle(queue chan []byte) {
defer p.wg.Done()
for d := range queue {
p.post(d)
}
}
func (p *peer) send(d []byte) error {
p.mu.Lock()
defer p.mu.Unlock()
switch p.status {
case participantPeer:
select {
case p.queue <- d:
default:
return fmt.Errorf("reach max serving")
}
case idlePeer:
if p.inflight.Get() > maxInflight {
return fmt.Errorf("reach max idle")
}
p.wg.Add(1)
go func() {
p.post(d)
p.wg.Done()
}()
case stoppedPeer:
return fmt.Errorf("sender stopped")
}
return nil
}
func (p *peer) post(d []byte) {
p.inflight.Add(1)
defer p.inflight.Add(-1)
buf := bytes.NewBuffer(d)
start := time.Now()
resp, err := p.c.Post(p.url, "application/octet-stream", buf)
end := time.Now()
// TODO: Have no way to detect RPC success or failure now
p.followerStats.Succ(end.Sub(start))
if err != nil {
log.Printf("peer.post url=%s err=\"%v\"", p.url, err)
return
}
resp.Body.Close()
}
// An AtomicInt is an int64 to be accessed atomically.
type atomicInt int64
func (i *atomicInt) Add(d int64) {
atomic.AddInt64((*int64)(i), d)
}
func (i *atomicInt) Get() int64 {
return atomic.LoadInt64((*int64)(i))
}
func (i *atomicInt) Set(n int64) {
atomic.StoreInt64((*int64)(i), n)
}

View File

@ -1,174 +0,0 @@
/*
Copyright 2014 CoreOS Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package etcdserver
import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"path"
"sync"
"time"
"github.com/coreos/etcd/raft"
)
var (
errUnknownPeer = errors.New("unknown peer")
)
type peerGetter interface {
peer(id int64) (*peer, error)
}
type peerHub struct {
mu sync.RWMutex
stopped bool
seeds map[string]bool
peers map[int64]*peer
c *http.Client
followersStats *raftFollowersStats
serverStats *raftServerStats
}
func newPeerHub(c *http.Client, followersStats *raftFollowersStats) *peerHub {
h := &peerHub{
peers: make(map[int64]*peer),
seeds: make(map[string]bool),
c: c,
followersStats: followersStats,
}
return h
}
func (h *peerHub) setServerStats(serverStats *raftServerStats) {
h.serverStats = serverStats
}
func (h *peerHub) setSeeds(seeds []string) {
for _, seed := range seeds {
h.seeds[seed] = true
}
}
func (h *peerHub) getSeeds() map[string]bool {
h.mu.RLock()
defer h.mu.RUnlock()
s := make(map[string]bool)
for k, v := range h.seeds {
s[k] = v
}
return s
}
func (h *peerHub) stop() {
h.mu.Lock()
defer h.mu.Unlock()
h.stopped = true
for _, p := range h.peers {
p.stop()
}
h.followersStats.Reset()
// http.Transport needs some time to put used connections
// into idle queues.
time.Sleep(time.Millisecond)
tr := h.c.Transport.(*http.Transport)
tr.CloseIdleConnections()
}
func (h *peerHub) peer(id int64) (*peer, error) {
h.mu.Lock()
defer h.mu.Unlock()
if h.stopped {
return nil, fmt.Errorf("peerHub stopped")
}
if p, ok := h.peers[id]; ok {
return p, nil
}
return nil, fmt.Errorf("peer %d not found", id)
}
func (h *peerHub) add(id int64, rawurl string) (*peer, error) {
u, err := url.Parse(rawurl)
if err != nil {
return nil, err
}
u.Path = raftPrefix
h.mu.Lock()
defer h.mu.Unlock()
if h.stopped {
return nil, fmt.Errorf("peerHub stopped")
}
h.peers[id] = newPeer(u.String(), h.c, h.followersStats.Follower(fmt.Sprint(id)))
return h.peers[id], nil
}
func (h *peerHub) send(msg raft.Message) error {
if p, err := h.fetch(msg.To); err == nil {
data, err := json.Marshal(msg)
if err != nil {
return err
}
if msg.IsMsgApp() {
h.serverStats.SendAppendReq(len(data))
}
p.send(data)
return nil
}
return errUnknownPeer
}
func (h *peerHub) fetch(nodeId int64) (*peer, error) {
if p, err := h.peer(nodeId); err == nil {
return p, nil
}
for seed := range h.seeds {
if p, err := h.seedFetch(seed, nodeId); err == nil {
return p, nil
}
}
return nil, fmt.Errorf("cannot fetch the address of node %d", nodeId)
}
func (h *peerHub) seedFetch(seedurl string, id int64) (*peer, error) {
u, err := url.Parse(seedurl)
if err != nil {
return nil, fmt.Errorf("cannot parse the url of the given seed")
}
u.Path = path.Join("/raft/cfg", fmt.Sprint(id))
resp, err := h.c.Get(u.String())
if err != nil {
return nil, fmt.Errorf("cannot reach %v", u)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("cannot find node %d via %s", id, seedurl)
}
b, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("cannot reach %v", u)
}
return h.add(id, string(b))
}

View File

@ -1,77 +0,0 @@
package etcdserver
import (
"math"
"time"
)
type raftFollowersStats struct {
Leader string `json:"leader"`
Followers map[string]*raftFollowerStats `json:"followers"`
}
func NewRaftFollowersStats(name string) *raftFollowersStats {
return &raftFollowersStats{
Leader: name,
Followers: make(map[string]*raftFollowerStats),
}
}
func (fs *raftFollowersStats) Follower(name string) *raftFollowerStats {
follower, ok := fs.Followers[name]
if !ok {
follower = &raftFollowerStats{}
follower.Latency.Minimum = 1 << 63
fs.Followers[name] = follower
}
return follower
}
func (fs *raftFollowersStats) Reset() {
fs.Followers = make(map[string]*raftFollowerStats)
}
type raftFollowerStats struct {
Latency struct {
Current float64 `json:"current"`
Average float64 `json:"average"`
averageSquare float64
StandardDeviation float64 `json:"standardDeviation"`
Minimum float64 `json:"minimum"`
Maximum float64 `json:"maximum"`
} `json:"latency"`
Counts struct {
Fail uint64 `json:"fail"`
Success uint64 `json:"success"`
} `json:"counts"`
}
// Succ function update the raftFollowerStats with a successful send
func (ps *raftFollowerStats) Succ(d time.Duration) {
total := float64(ps.Counts.Success) * ps.Latency.Average
totalSquare := float64(ps.Counts.Success) * ps.Latency.averageSquare
ps.Counts.Success++
ps.Latency.Current = float64(d) / (1000000.0)
if ps.Latency.Current > ps.Latency.Maximum {
ps.Latency.Maximum = ps.Latency.Current
}
if ps.Latency.Current < ps.Latency.Minimum {
ps.Latency.Minimum = ps.Latency.Current
}
ps.Latency.Average = (total + ps.Latency.Current) / float64(ps.Counts.Success)
ps.Latency.averageSquare = (totalSquare + ps.Latency.Current*ps.Latency.Current) / float64(ps.Counts.Success)
// sdv = sqrt(avg(x^2) - avg(x)^2)
ps.Latency.StandardDeviation = math.Sqrt(ps.Latency.averageSquare - ps.Latency.Average*ps.Latency.Average)
}
// Fail function update the raftFollowerStats with a unsuccessful send
func (ps *raftFollowerStats) Fail() {
ps.Counts.Fail++
}

View File

@ -1,140 +0,0 @@
/*
Copyright 2014 CoreOS Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package etcdserver
import (
"encoding/json"
"fmt"
"log"
"net/http"
"strconv"
"sync"
"github.com/coreos/etcd/raft"
"github.com/coreos/etcd/store"
)
const (
raftPrefix = "/raft"
)
type raftHandler struct {
mu sync.RWMutex
serving bool
peerGetter peerGetter
storeVersion int
serverStats *raftServerStats
recv chan *raft.Message
*http.ServeMux
}
func newRaftHandler(p peerGetter, version int, serverStats *raftServerStats) *raftHandler {
h := &raftHandler{
recv: make(chan *raft.Message, 512),
peerGetter: p,
storeVersion: version,
serverStats: serverStats,
}
h.ServeMux = http.NewServeMux()
h.ServeMux.HandleFunc(raftPrefix+"/cfg/", h.serveCfg)
h.ServeMux.HandleFunc(raftPrefix, h.serveRaft)
h.ServeMux.HandleFunc(raftPrefix+"/version", h.serveVersion)
h.ServeMux.HandleFunc(raftPrefix+"/version/", h.serveVersionCheck)
return h
}
func (h *raftHandler) start() {
h.mu.Lock()
h.serving = true
h.mu.Unlock()
}
func (h *raftHandler) stop() {
h.mu.Lock()
h.serving = false
h.mu.Unlock()
}
func (h *raftHandler) serveRaft(w http.ResponseWriter, r *http.Request) {
h.mu.RLock()
serving := h.serving
h.mu.RUnlock()
if !serving {
http.Error(w, "404 page not found", http.StatusNotFound)
return
}
msg := new(raft.Message)
if err := json.NewDecoder(r.Body).Decode(msg); err != nil {
log.Printf("raftHandler.serve decodeErr=\"%v\"\n", err)
return
}
if msg.IsMsgApp() {
h.serverStats.RecvAppendReq(fmt.Sprint(msg.From), int(r.ContentLength))
}
select {
case h.recv <- msg:
default:
log.Printf("raftHandler.serve pushErr=\"recv channel is full\"\n")
// drop the incoming package at network layer if the upper layer
// cannot consume them in time.
// TODO(xiangli): not return 200.
}
return
}
func (h *raftHandler) serveCfg(w http.ResponseWriter, r *http.Request) {
h.mu.RLock()
serving := h.serving
h.mu.RUnlock()
if !serving {
http.Error(w, "404 page not found", http.StatusNotFound)
return
}
id, err := strconv.ParseInt(r.URL.Path[len("/raft/cfg/"):], 10, 64)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
p, err := h.peerGetter.peer(id)
if err == nil {
w.Write([]byte(p.url))
return
}
http.Error(w, err.Error(), http.StatusNotFound)
}
func (h *raftHandler) serveVersion(w http.ResponseWriter, req *http.Request) {
w.Write([]byte(strconv.Itoa(h.storeVersion)))
}
func (h *raftHandler) serveVersionCheck(w http.ResponseWriter, req *http.Request) {
var version int
n, err := fmt.Sscanf(req.URL.Path, raftPrefix+"/version/%d/check", &version)
if err != nil || n != 1 {
http.Error(w, "error version check format: "+req.URL.Path, http.StatusBadRequest)
return
}
if version < store.MinVersion() || version > store.MaxVersion() {
w.WriteHeader(http.StatusForbidden)
return
}
}

View File

@ -1,83 +0,0 @@
package etcdserver
import (
"sync"
"time"
)
type raftServerStats struct {
Name string `json:"name"`
State string `json:"state"`
StartTime time.Time `json:"startTime"`
LeaderInfo struct {
Name string `json:"leader"`
Uptime string `json:"uptime"`
StartTime time.Time `json:"startTime"`
} `json:"leaderInfo"`
RecvAppendRequestCnt uint64 `json:"recvAppendRequestCnt,"`
RecvingPkgRate float64 `json:"recvPkgRate,omitempty"`
RecvingBandwidthRate float64 `json:"recvBandwidthRate,omitempty"`
SendAppendRequestCnt uint64 `json:"sendAppendRequestCnt"`
SendingPkgRate float64 `json:"sendPkgRate,omitempty"`
SendingBandwidthRate float64 `json:"sendBandwidthRate,omitempty"`
sendRateQueue *statsQueue
recvRateQueue *statsQueue
sync.Mutex
}
func NewRaftServerStats(name string) *raftServerStats {
stats := &raftServerStats{
Name: name,
StartTime: time.Now(),
sendRateQueue: &statsQueue{
back: -1,
},
recvRateQueue: &statsQueue{
back: -1,
},
}
stats.LeaderInfo.StartTime = time.Now()
return stats
}
func (ss *raftServerStats) Reset() {
name := ss.Name
ss = NewRaftServerStats(name)
return
}
func (ss *raftServerStats) RecvAppendReq(leaderName string, pkgSize int) {
ss.Lock()
defer ss.Unlock()
ss.State = stateFollower
if leaderName != ss.LeaderInfo.Name {
ss.LeaderInfo.Name = leaderName
ss.LeaderInfo.StartTime = time.Now()
}
ss.recvRateQueue.Insert(NewPackageStats(time.Now(), pkgSize))
ss.RecvAppendRequestCnt++
}
func (ss *raftServerStats) SendAppendReq(pkgSize int) {
ss.Lock()
defer ss.Unlock()
now := time.Now()
if ss.State != stateLeader {
ss.State = stateLeader
ss.LeaderInfo.Name = ss.Name
ss.LeaderInfo.StartTime = now
}
ss.sendRateQueue.Insert(NewPackageStats(now, pkgSize))
ss.SendAppendRequestCnt++
}

View File

@ -5,7 +5,7 @@ import (
"time" "time"
"code.google.com/p/go.net/context" "code.google.com/p/go.net/context"
pb "github.com/coreos/etcd/etcdserver2/etcdserverpb" pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
"github.com/coreos/etcd/raft" "github.com/coreos/etcd/raft"
"github.com/coreos/etcd/raft/raftpb" "github.com/coreos/etcd/raft/raftpb"
"github.com/coreos/etcd/store" "github.com/coreos/etcd/store"

View File

@ -5,9 +5,10 @@ import (
"reflect" "reflect"
"testing" "testing"
"time" "time"
"code.google.com/p/go.net/context" "code.google.com/p/go.net/context"
pb "github.com/coreos/etcd/etcdserver2/etcdserverpb" pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
"github.com/coreos/etcd/raft" "github.com/coreos/etcd/raft"
"github.com/coreos/etcd/raft/raftpb" "github.com/coreos/etcd/raft/raftpb"
"github.com/coreos/etcd/store" "github.com/coreos/etcd/store"

View File

@ -1,137 +0,0 @@
/*
Copyright 2014 CoreOS Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package etcdserver
import (
"fmt"
"log"
"net/http"
"strconv"
"sync"
"time"
"github.com/coreos/etcd/conf"
)
var (
noneId int64 = -1
)
type standby struct {
client *v2client
peerHub *peerHub
leader int64
leaderAddr string
mu sync.RWMutex
clusterConf *conf.ClusterConfig
*http.ServeMux
}
func newStandby(client *v2client, peerHub *peerHub) *standby {
s := &standby{
client: client,
peerHub: peerHub,
leader: noneId,
leaderAddr: "",
clusterConf: conf.NewClusterConfig(),
ServeMux: http.NewServeMux(),
}
s.Handle("/", handlerErr(s.serveRedirect))
return s
}
func (s *standby) run(stop chan struct{}) {
syncDuration := time.Millisecond * 100
nodes := s.peerHub.getSeeds()
for {
select {
case <-time.After(syncDuration):
case <-stop:
log.Printf("standby.stop\n")
return
}
if update, err := s.syncCluster(nodes); err != nil {
log.Printf("standby.run syncErr=\"%v\"", err)
continue
} else {
nodes = update
}
syncDuration = time.Duration(s.clusterConf.SyncInterval * float64(time.Second))
if s.clusterConf.ActiveSize <= len(nodes) {
continue
}
log.Printf("standby.end\n")
return
}
}
func (s *standby) leaderInfo() (int64, string) {
s.mu.RLock()
defer s.mu.RUnlock()
return s.leader, s.leaderAddr
}
func (s *standby) setLeaderInfo(leader int64, leaderAddr string) {
s.mu.Lock()
defer s.mu.Unlock()
s.leader, s.leaderAddr = leader, leaderAddr
}
func (s *standby) serveRedirect(w http.ResponseWriter, r *http.Request) error {
leader, leaderAddr := s.leaderInfo()
if leader == noneId {
return fmt.Errorf("no leader in the cluster")
}
redirectAddr, err := buildRedirectURL(leaderAddr, r.URL)
if err != nil {
return err
}
http.Redirect(w, r, redirectAddr, http.StatusTemporaryRedirect)
return nil
}
func (s *standby) syncCluster(nodes map[string]bool) (map[string]bool, error) {
for node := range nodes {
machines, err := s.client.GetMachines(node)
if err != nil {
continue
}
cfg, err := s.client.GetClusterConfig(node)
if err != nil {
continue
}
nn := make(map[string]bool)
for _, machine := range machines {
nn[machine.PeerURL] = true
if machine.State == stateLeader {
id, err := strconv.ParseInt(machine.Name, 0, 64)
if err != nil {
return nil, err
}
s.setLeaderInfo(id, machine.PeerURL)
}
}
s.clusterConf = cfg
return nn, nil
}
return nil, fmt.Errorf("unreachable cluster")
}

View File

@ -1,89 +0,0 @@
package etcdserver
import (
"sync"
"time"
)
const (
queueCapacity = 200
)
type statsQueue struct {
items [queueCapacity]*packageStats
size int
front int
back int
totalPkgSize int
rwl sync.RWMutex
}
func (q *statsQueue) Len() int {
return q.size
}
func (q *statsQueue) PkgSize() int {
return q.totalPkgSize
}
// FrontAndBack gets the front and back elements in the queue
// We must grab front and back together with the protection of the lock
func (q *statsQueue) frontAndBack() (*packageStats, *packageStats) {
q.rwl.RLock()
defer q.rwl.RUnlock()
if q.size != 0 {
return q.items[q.front], q.items[q.back]
}
return nil, nil
}
// Insert function insert a packageStats into the queue and update the records
func (q *statsQueue) Insert(p *packageStats) {
q.rwl.Lock()
defer q.rwl.Unlock()
q.back = (q.back + 1) % queueCapacity
if q.size == queueCapacity { //dequeue
q.totalPkgSize -= q.items[q.front].size
q.front = (q.back + 1) % queueCapacity
} else {
q.size++
}
q.items[q.back] = p
q.totalPkgSize += q.items[q.back].size
}
// Rate function returns the package rate and byte rate
func (q *statsQueue) Rate() (float64, float64) {
front, back := q.frontAndBack()
if front == nil || back == nil {
return 0, 0
}
if time.Now().Sub(back.Time()) > time.Second {
q.Clear()
return 0, 0
}
sampleDuration := back.Time().Sub(front.Time())
pr := float64(q.Len()) / float64(sampleDuration) * float64(time.Second)
br := float64(q.PkgSize()) / float64(sampleDuration) * float64(time.Second)
return pr, br
}
// Clear function clear up the statsQueue
func (q *statsQueue) Clear() {
q.rwl.Lock()
defer q.rwl.Unlock()
q.back = -1
q.front = 0
q.size = 0
q.totalPkgSize = 0
}

View File

@ -1,182 +0,0 @@
/*
Copyright 2014 CoreOS Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package etcdserver
import (
"encoding/json"
"fmt"
"net/http"
"net/url"
"path/filepath"
"strconv"
"strings"
"github.com/coreos/etcd/conf"
"github.com/coreos/etcd/store"
)
const (
stateFollower = "follower"
stateCandidate = "candidate"
stateLeader = "leader"
)
// machineMessage represents information about a peer or standby in the registry.
type machineMessage struct {
Name string `json:"name"`
State string `json:"state"`
ClientURL string `json:"clientURL"`
PeerURL string `json:"peerURL"`
}
type context struct {
MinVersion int `json:"minVersion"`
MaxVersion int `json:"maxVersion"`
ClientURL string `json:"clientURL"`
PeerURL string `json:"peerURL"`
}
func (p *participant) serveAdminConfig(w http.ResponseWriter, r *http.Request) error {
switch r.Method {
case "GET":
case "PUT":
if !p.node.IsLeader() {
return p.redirect(w, r, p.node.Leader())
}
c := p.clusterConfig()
if err := json.NewDecoder(r.Body).Decode(c); err != nil {
return err
}
c.Sanitize()
if err := p.setClusterConfig(c); err != nil {
return err
}
default:
return allow(w, "GET", "PUT")
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(p.clusterConfig())
return nil
}
func (p *participant) serveAdminMachines(w http.ResponseWriter, r *http.Request) error {
name := strings.TrimPrefix(r.URL.Path, v2adminMachinesPrefix)
switch r.Method {
case "GET":
var info interface{}
var err error
if name != "" {
info, err = p.machineMessage(name)
} else {
info, err = p.allMachineMessages()
}
if err != nil {
return err
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(info)
case "PUT":
if !p.node.IsLeader() {
return p.redirect(w, r, p.node.Leader())
}
id, err := strconv.ParseInt(name, 0, 64)
if err != nil {
return err
}
info := &context{}
if err := json.NewDecoder(r.Body).Decode(info); err != nil {
return err
}
return p.add(id, info.PeerURL, info.ClientURL)
case "DELETE":
if !p.node.IsLeader() {
return p.redirect(w, r, p.node.Leader())
}
id, err := strconv.ParseInt(name, 0, 64)
if err != nil {
return err
}
return p.remove(id)
default:
return allow(w, "GET", "PUT", "DELETE")
}
return nil
}
func (p *participant) clusterConfig() *conf.ClusterConfig {
c := conf.NewClusterConfig()
// This is used for backward compatibility because it doesn't
// set cluster config in older version.
if e, err := p.Store.Get(v2configKVPrefix, false, false); err == nil {
json.Unmarshal([]byte(*e.Node.Value), c)
}
return c
}
func (p *participant) setClusterConfig(c *conf.ClusterConfig) error {
b, err := json.Marshal(c)
if err != nil {
return err
}
if _, err := p.Set(v2configKVPrefix, false, string(b), store.Permanent); err != nil {
return err
}
return nil
}
// machineMessage returns the machineMessage of the given name
func (p *participant) machineMessage(name string) (*machineMessage, error) {
pp := filepath.Join(v2machineKVPrefix, name)
e, err := p.Store.Get(pp, false, false)
if err != nil {
return nil, err
}
lead := fmt.Sprint(p.node.Leader())
return newMachineMessage(e.Node, lead), nil
}
func (p *participant) allMachineMessages() ([]*machineMessage, error) {
e, err := p.Store.Get(v2machineKVPrefix, false, false)
if err != nil {
return nil, err
}
lead := fmt.Sprint(p.node.Leader())
ms := make([]*machineMessage, len(e.Node.Nodes))
for i, n := range e.Node.Nodes {
ms[i] = newMachineMessage(n, lead)
}
return ms, nil
}
func newMachineMessage(n *store.NodeExtern, lead string) *machineMessage {
_, name := filepath.Split(n.Key)
q, err := url.ParseQuery(*n.Value)
if err != nil {
panic("fail to parse the info for machine " + name)
}
m := &machineMessage{
Name: name,
State: stateFollower,
ClientURL: q["etcd"][0],
PeerURL: q["raft"][0],
}
if name == lead {
m.State = stateLeader
}
return m
}

View File

@ -1,99 +0,0 @@
/*
Copyright 2014 CoreOS Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package etcdserver
import (
"fmt"
"log"
"time"
"github.com/coreos/etcd/raft"
"github.com/coreos/etcd/store"
)
func (p *participant) v2apply(index int64, ent raft.Entry) {
var ret interface{}
var e *store.Event
var err error
var cmd Cmd
if err := cmd.Unmarshal(ent.Data); err != nil {
log.Printf("id=%x participant.store.apply decodeErr=\"%v\"\n", p.id, err)
return
}
switch cmd.Type {
case stset:
e, err = p.Store.Set(cmd.Key, *cmd.Dir, *cmd.Value, mustUnmarshalTime(cmd.Time))
case stupdate:
e, err = p.Store.Update(cmd.Key, *cmd.Value, mustUnmarshalTime(cmd.Time))
case stcreate:
e, err = p.Store.Create(cmd.Key, *cmd.Dir, *cmd.Value, *cmd.Unique, mustUnmarshalTime(cmd.Time))
case stdelete:
e, err = p.Store.Delete(cmd.Key, *cmd.Dir, *cmd.Recursive)
case stcad:
e, err = p.Store.CompareAndDelete(cmd.Key, *cmd.PrevValue, *cmd.PrevIndex)
case stcas:
e, err = p.Store.CompareAndSwap(cmd.Key, *cmd.PrevValue, *cmd.PrevIndex, *cmd.Value, mustUnmarshalTime(cmd.Time))
case stqget:
e, err = p.Store.Get(cmd.Key, *cmd.Recursive, *cmd.Sorted)
case stsync:
p.Store.DeleteExpiredKeys(mustUnmarshalTime(cmd.Time))
return
default:
log.Printf("id=%x participant.store.apply err=\"unexpected command type %s\"\n", p.id, cmd.Type)
}
if ent.Term > p.node.term {
p.node.term = ent.Term
for k, v := range p.node.result {
if k.term < p.node.term {
v <- fmt.Errorf("proposal lost due to leader election")
delete(p.node.result, k)
}
}
}
w := wait{index, ent.Term}
if p.node.result[w] == nil {
return
}
if err != nil {
ret = err
} else {
ret = e
}
p.node.result[w] <- ret
delete(p.node.result, w)
}
func mustMarshalTime(t *time.Time) []byte {
b, err := t.MarshalBinary()
if err != nil {
panic(err)
}
return b
}
func mustUnmarshalTime(b []byte) time.Time {
var time time.Time
if err := time.UnmarshalBinary(b); err != nil {
panic(err)
}
return time
}

View File

@ -1,254 +0,0 @@
/*
Copyright 2014 CoreOS Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package etcdserver
import (
"bytes"
"crypto/tls"
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"net/http"
"strconv"
"strings"
"sync"
"github.com/coreos/etcd/conf"
etcdErr "github.com/coreos/etcd/error"
)
// v2client sends various requests using HTTP API.
// It is different from raft communication, and doesn't record anything in the log.
// The argument url is required to contain scheme and host only, and
// there is no trailing slash in it.
// Public functions return "etcd/error".Error intentionally to figure out
// etcd error code easily.
type v2client struct {
stopped bool
mu sync.RWMutex
http.Client
wg sync.WaitGroup
}
func newClient(tc *tls.Config) *v2client {
tr := new(http.Transport)
tr.TLSClientConfig = tc
return &v2client{Client: http.Client{Transport: tr}}
}
func (c *v2client) CloseConnections() {
c.stop()
c.wg.Wait()
tr := c.Transport.(*http.Transport)
tr.CloseIdleConnections()
}
// CheckVersion returns true when the version check on the server returns 200.
func (c *v2client) CheckVersion(url string, version int) (bool, *etcdErr.Error) {
if c.runOne() == false {
return false, clientError(errors.New("v2_client is stopped"))
}
defer c.finishOne()
resp, err := c.Get(url + fmt.Sprintf("/version/%d/check", version))
if err != nil {
return false, clientError(err)
}
c.readBody(resp.Body)
return resp.StatusCode == 200, nil
}
// GetVersion fetches the peer version of a cluster.
func (c *v2client) GetVersion(url string) (int, *etcdErr.Error) {
if c.runOne() == false {
return 0, clientError(errors.New("v2_client is stopped"))
}
defer c.finishOne()
resp, err := c.Get(url + "/version")
if err != nil {
return 0, clientError(err)
}
body, err := c.readBody(resp.Body)
if err != nil {
return 0, clientError(err)
}
// Parse version number.
version, err := strconv.Atoi(string(body))
if err != nil {
return 0, clientError(err)
}
return version, nil
}
func (c *v2client) GetMachines(url string) ([]*machineMessage, *etcdErr.Error) {
if c.runOne() == false {
return nil, clientError(errors.New("v2_client is stopped"))
}
defer c.finishOne()
resp, err := c.Get(url + "/v2/admin/machines/")
if err != nil {
return nil, clientError(err)
}
if resp.StatusCode != http.StatusOK {
return nil, c.readErrorBody(resp.Body)
}
msgs := new([]*machineMessage)
if uerr := c.readJSONBody(resp.Body, msgs); uerr != nil {
return nil, uerr
}
return *msgs, nil
}
func (c *v2client) GetClusterConfig(url string) (*conf.ClusterConfig, *etcdErr.Error) {
if c.runOne() == false {
return nil, clientError(errors.New("v2_client is stopped"))
}
defer c.finishOne()
resp, err := c.Get(url + "/v2/admin/config")
if err != nil {
return nil, clientError(err)
}
if resp.StatusCode != http.StatusOK {
return nil, c.readErrorBody(resp.Body)
}
cfg := new(conf.ClusterConfig)
if uerr := c.readJSONBody(resp.Body, cfg); uerr != nil {
return nil, uerr
}
return cfg, nil
}
// AddMachine adds machine to the cluster.
// The first return value is the commit index of join command.
func (c *v2client) AddMachine(url string, name string, info *context) *etcdErr.Error {
if c.runOne() == false {
return clientError(errors.New("v2_client is stopped"))
}
defer c.finishOne()
b, _ := json.Marshal(info)
url = url + "/v2/admin/machines/" + name
resp, err := c.put(url, b)
if err != nil {
return clientError(err)
}
if resp.StatusCode != http.StatusOK {
return c.readErrorBody(resp.Body)
}
c.readBody(resp.Body)
return nil
}
func (c *v2client) readErrorBody(body io.ReadCloser) *etcdErr.Error {
b, err := c.readBody(body)
if err != nil {
return clientError(err)
}
uerr := &etcdErr.Error{}
if err := json.Unmarshal(b, uerr); err != nil {
str := strings.TrimSpace(string(b))
return etcdErr.NewError(etcdErr.EcodeClientInternal, str, 0)
}
return nil
}
func (c *v2client) readJSONBody(body io.ReadCloser, val interface{}) *etcdErr.Error {
if err := json.NewDecoder(body).Decode(val); err != nil {
return clientError(err)
}
c.readBody(body)
return nil
}
func (c *v2client) readBody(body io.ReadCloser) ([]byte, error) {
b, err := ioutil.ReadAll(body)
body.Close()
return b, err
}
// put sends server side PUT request.
// It always follows redirects instead of stopping according to RFC 2616.
func (c *v2client) put(urlStr string, body []byte) (*http.Response, error) {
return c.doAlwaysFollowingRedirects("PUT", urlStr, body)
}
func (c *v2client) doAlwaysFollowingRedirects(method string, urlStr string, body []byte) (resp *http.Response, err error) {
var req *http.Request
for redirect := 0; redirect < 10; redirect++ {
req, err = http.NewRequest(method, urlStr, bytes.NewBuffer(body))
if err != nil {
return
}
if resp, err = c.Do(req); err != nil {
if resp != nil {
resp.Body.Close()
}
return
}
if resp.StatusCode == http.StatusMovedPermanently || resp.StatusCode == http.StatusTemporaryRedirect {
resp.Body.Close()
if urlStr = resp.Header.Get("Location"); urlStr == "" {
err = errors.New(fmt.Sprintf("%d response missing Location header", resp.StatusCode))
return
}
continue
}
return
}
err = errors.New("stopped after 10 redirects")
return
}
func (c *v2client) runOne() bool {
c.mu.RLock()
defer c.mu.RUnlock()
if c.stopped {
return false
}
c.wg.Add(1)
return true
}
func (c *v2client) finishOne() {
c.wg.Done()
}
func (c *v2client) stop() {
c.mu.Lock()
c.stopped = true
c.mu.Unlock()
}
func clientError(err error) *etcdErr.Error {
return etcdErr.NewError(etcdErr.EcodeClientInternal, err.Error(), 0)
}

View File

@ -1,174 +0,0 @@
/*
Copyright 2014 CoreOS Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package etcdserver
import (
"encoding/json"
"fmt"
"log"
"net/http"
"net/url"
"strings"
"time"
etcdErr "github.com/coreos/etcd/error"
)
func (p *participant) serveValue(w http.ResponseWriter, r *http.Request) error {
switch r.Method {
case "GET":
return p.GetHandler(w, r)
case "HEAD":
w = &HEADResponseWriter{w}
return p.GetHandler(w, r)
case "PUT":
return p.PutHandler(w, r)
case "POST":
return p.PostHandler(w, r)
case "DELETE":
return p.DeleteHandler(w, r)
}
return allow(w, "GET", "PUT", "POST", "DELETE", "HEAD")
}
func (p *participant) serveMachines(w http.ResponseWriter, r *http.Request) error {
if r.Method != "GET" {
return allow(w, "GET")
}
v, err := p.Store.Get(v2machineKVPrefix, false, false)
if err != nil {
panic(err)
}
ns := make([]string, len(v.Node.Nodes))
for i, n := range v.Node.Nodes {
m, err := url.ParseQuery(*n.Value)
if err != nil {
continue
}
ns[i] = m["etcd"][0]
}
w.Write([]byte(strings.Join(ns, ",")))
return nil
}
func (p *participant) serveLeader(w http.ResponseWriter, r *http.Request) error {
if r.Method != "GET" {
return allow(w, "GET")
}
if p, ok := p.peerHub.peers[p.node.Leader()]; ok {
w.Write([]byte(p.url))
return nil
}
return fmt.Errorf("no leader")
}
func (p *participant) serveSelfStats(w http.ResponseWriter, req *http.Request) error {
p.serverStats.LeaderInfo.Uptime = time.Now().Sub(p.serverStats.LeaderInfo.StartTime).String()
if p.node.IsLeader() {
p.serverStats.LeaderInfo.Name = fmt.Sprint(p.id)
}
queue := p.serverStats.sendRateQueue
p.serverStats.SendingPkgRate, p.serverStats.SendingBandwidthRate = queue.Rate()
queue = p.serverStats.recvRateQueue
p.serverStats.RecvingPkgRate, p.serverStats.RecvingBandwidthRate = queue.Rate()
return json.NewEncoder(w).Encode(p.serverStats)
}
func (p *participant) serveLeaderStats(w http.ResponseWriter, req *http.Request) error {
if !p.node.IsLeader() {
return p.redirect(w, req, p.node.Leader())
}
w.Header().Set("Content-Type", "application/json")
return json.NewEncoder(w).Encode(p.peerHub.followersStats)
}
func (p *participant) serveStoreStats(w http.ResponseWriter, req *http.Request) error {
w.Header().Set("Content-Type", "application/json")
w.Write(p.Store.JsonStats())
return nil
}
type handlerErr func(w http.ResponseWriter, r *http.Request) error
func (eh handlerErr) ServeHTTP(w http.ResponseWriter, r *http.Request) {
err := eh(w, r)
if err == nil {
return
}
if r.Method == "HEAD" {
w = &HEADResponseWriter{w}
}
if etcdErr, ok := err.(*etcdErr.Error); ok {
w.Header().Set("Content-Type", "application/json")
etcdErr.Write(w)
return
}
log.Printf("HTTP.serve: req=%s err=\"%v\"\n", r.URL, err)
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
}
func allow(w http.ResponseWriter, m ...string) error {
w.Header().Set("Allow", strings.Join(m, ","))
return nil
}
type HEADResponseWriter struct {
http.ResponseWriter
}
func (w *HEADResponseWriter) Write([]byte) (int, error) {
return 0, nil
}
func (p *participant) redirect(w http.ResponseWriter, r *http.Request, id int64) error {
e, err := p.Store.Get(fmt.Sprintf("%v/%d", v2machineKVPrefix, p.node.Leader()), false, false)
if err != nil {
return fmt.Errorf("redirect cannot find node %d", id)
}
m, err := url.ParseQuery(*e.Node.Value)
if err != nil {
return fmt.Errorf("failed to parse node entry: %s", *e.Node.Value)
}
redirectAddr, err := buildRedirectURL(m["etcd"][0], r.URL)
if err != nil {
return err
}
http.Redirect(w, r, redirectAddr, http.StatusTemporaryRedirect)
return nil
}
func buildRedirectURL(redirectAddr string, originalURL *url.URL) (string, error) {
redirectURL, err := url.Parse(redirectAddr)
if err != nil {
return "", fmt.Errorf("cannot parse url: %v", err)
}
redirectURL.Path = originalURL.Path
redirectURL.RawQuery = originalURL.RawQuery
redirectURL.Fragment = originalURL.Fragment
return redirectURL.String(), nil
}

View File

@ -1,82 +0,0 @@
/*
Copyright 2014 CoreOS Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package etcdserver
import (
"net/http"
"strconv"
etcdErr "github.com/coreos/etcd/error"
)
func (p *participant) DeleteHandler(w http.ResponseWriter, req *http.Request) error {
if !p.node.IsLeader() {
return p.redirect(w, req, p.node.Leader())
}
key := req.URL.Path[len("/v2/keys"):]
recursive := (req.FormValue("recursive") == "true")
dir := (req.FormValue("dir") == "true")
req.ParseForm()
_, valueOk := req.Form["prevValue"]
_, indexOk := req.Form["prevIndex"]
if !valueOk && !indexOk {
return p.serveDelete(w, req, key, dir, recursive)
}
var err error
prevIndex := uint64(0)
prevValue := req.Form.Get("prevValue")
if indexOk {
prevIndexStr := req.Form.Get("prevIndex")
prevIndex, err = strconv.ParseUint(prevIndexStr, 10, 64)
// bad previous index
if err != nil {
return etcdErr.NewError(etcdErr.EcodeIndexNaN, "CompareAndDelete", p.Store.Index())
}
}
if valueOk {
if prevValue == "" {
return etcdErr.NewError(etcdErr.EcodePrevValueRequired, "CompareAndDelete", p.Store.Index())
}
}
return p.serveCAD(w, req, key, prevValue, prevIndex)
}
func (p *participant) serveDelete(w http.ResponseWriter, req *http.Request, key string, dir, recursive bool) error {
ret, err := p.Delete(key, dir, recursive)
if err == nil {
p.handleRet(w, ret)
return nil
}
return err
}
func (p *participant) serveCAD(w http.ResponseWriter, req *http.Request, key string, prevValue string, prevIndex uint64) error {
ret, err := p.CAD(key, prevValue, prevIndex)
if err == nil {
p.handleRet(w, ret)
return nil
}
return err
}

View File

@ -1,293 +0,0 @@
/*
Copyright 2014 CoreOS Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package etcdserver
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"reflect"
"sort"
"strings"
"testing"
"github.com/coreos/etcd/conf"
"github.com/coreos/etcd/store"
)
func TestMachinesEndPoint(t *testing.T) {
cl := &testCluster{Size: 3}
cl.Start()
w := make([]string, cl.Size)
for i := 0; i < cl.Size; i++ {
w[i] = cl.URL(i)
}
for i := 0; i < cl.Size; i++ {
r, err := http.Get(cl.URL(i) + v2machinePrefix)
if err != nil {
t.Errorf("%v", err)
break
}
b, err := ioutil.ReadAll(r.Body)
r.Body.Close()
if err != nil {
t.Errorf("%v", err)
break
}
g := strings.Split(string(b), ",")
sort.Strings(g)
if !reflect.DeepEqual(w, g) {
t.Errorf("machines = %v, want %v", g, w)
}
}
cl.Destroy()
}
func TestLeaderEndPoint(t *testing.T) {
cl := &testCluster{Size: 3}
cl.Start()
us := make([]string, cl.Size)
for i := 0; i < cl.Size; i++ {
us[i] = cl.URL(i)
}
// todo(xiangli) change this to raft port...
w := cl.URL(0) + "/raft"
for i := 0; i < cl.Size; i++ {
r, err := http.Get(cl.URL(i) + v2LeaderPrefix)
if err != nil {
t.Errorf("%v", err)
break
}
b, err := ioutil.ReadAll(r.Body)
r.Body.Close()
if err != nil {
t.Errorf("%v", err)
break
}
if string(b) != w {
t.Errorf("leader = %v, want %v", string(b), w)
}
}
cl.Destroy()
}
func TestStoreStatsEndPoint(t *testing.T) {
cl := &testCluster{Size: 1}
cl.Start()
resp, err := http.Get(cl.URL(0) + v2StoreStatsPrefix)
if err != nil {
t.Errorf("%v", err)
}
stats := new(store.Stats)
d := json.NewDecoder(resp.Body)
err = d.Decode(stats)
resp.Body.Close()
if err != nil {
t.Errorf("%v", err)
}
if stats.SetSuccess != 1 {
t.Errorf("setSuccess = %d, want 1", stats.SetSuccess)
}
cl.Destroy()
}
func TestGetAdminConfigEndPoint(t *testing.T) {
cl := &testCluster{Size: 1}
cl.Start()
r, err := http.Get(cl.URL(0) + v2adminConfigPrefix)
if err != nil {
t.Errorf("%v", err)
}
if g := r.StatusCode; g != 200 {
t.Errorf("status = %d, want %d", g, 200)
}
if g := r.Header.Get("Content-Type"); g != "application/json" {
t.Errorf("ContentType = %s, want application/json", g)
}
cfg := new(conf.ClusterConfig)
err = json.NewDecoder(r.Body).Decode(cfg)
r.Body.Close()
if err != nil {
t.Errorf("%v", err)
}
w := conf.NewClusterConfig()
if !reflect.DeepEqual(cfg, w) {
t.Errorf("config = %+v, want %+v", cfg, w)
}
cl.Destroy()
}
func TestPutAdminConfigEndPoint(t *testing.T) {
tests := []struct {
c, wc string
}{
{
`{"activeSize":1,"removeDelay":1,"syncInterval":1}`,
`{"activeSize":3,"removeDelay":2,"syncInterval":1}`,
},
{
`{"activeSize":5,"removeDelay":20.5,"syncInterval":1.5}`,
`{"activeSize":5,"removeDelay":20.5,"syncInterval":1.5}`,
},
{
`{"activeSize":5 , "removeDelay":20 , "syncInterval": 2 }`,
`{"activeSize":5,"removeDelay":20,"syncInterval":2}`,
},
{
`{"activeSize":3, "removeDelay":60}`,
`{"activeSize":3,"removeDelay":60,"syncInterval":5}`,
},
}
for i, tt := range tests {
cl := &testCluster{Size: 1}
cl.Start()
index := cl.Participant(0).Index()
r, err := NewTestClient().Put(cl.URL(0)+v2adminConfigPrefix, "application/json", bytes.NewBufferString(tt.c))
if err != nil {
t.Fatalf("%v", err)
}
b, err := ioutil.ReadAll(r.Body)
r.Body.Close()
if err != nil {
t.Fatalf("%v", err)
}
if wbody := append([]byte(tt.wc), '\n'); !reflect.DeepEqual(b, wbody) {
t.Errorf("#%d: put result = %s, want %s", i, b, wbody)
}
w, err := cl.Participant(0).Watch(v2configKVPrefix, false, false, index)
if err != nil {
t.Errorf("%v", err)
continue
}
e := <-w.EventChan
if g := *e.Node.Value; g != tt.wc {
t.Errorf("#%d: %s = %s, want %s", i, v2configKVPrefix, g, tt.wc)
}
cl.Destroy()
}
}
func TestGetAdminMachineEndPoint(t *testing.T) {
cl := &testCluster{Size: 3}
cl.Start()
for i := 0; i < cl.Size; i++ {
for j := 0; j < cl.Size; j++ {
name := fmt.Sprint(cl.Id(i))
r, err := http.Get(cl.URL(j) + v2adminMachinesPrefix + name)
if err != nil {
t.Errorf("%v", err)
continue
}
if g := r.StatusCode; g != 200 {
t.Errorf("#%d on %d: status = %d, want %d", i, j, g, 200)
}
if g := r.Header.Get("Content-Type"); g != "application/json" {
t.Errorf("#%d on %d: ContentType = %s, want application/json", i, j, g)
}
m := new(machineMessage)
err = json.NewDecoder(r.Body).Decode(m)
r.Body.Close()
if err != nil {
t.Errorf("%v", err)
continue
}
wm := &machineMessage{
Name: name,
State: stateFollower,
ClientURL: cl.URL(i),
PeerURL: cl.URL(i),
}
if i == 0 {
wm.State = stateLeader
}
if !reflect.DeepEqual(m, wm) {
t.Errorf("#%d on %d: body = %+v, want %+v", i, j, m, wm)
}
}
}
cl.Destroy()
}
func TestGetAdminMachinesEndPoint(t *testing.T) {
cl := &testCluster{Size: 3}
cl.Start()
w := make([]*machineMessage, cl.Size)
for i := 0; i < cl.Size; i++ {
w[i] = &machineMessage{
Name: fmt.Sprint(cl.Id(i)),
State: stateFollower,
ClientURL: cl.URL(i),
PeerURL: cl.URL(i),
}
}
w[0].State = stateLeader
for i := 0; i < cl.Size; i++ {
r, err := http.Get(cl.URL(i) + v2adminMachinesPrefix)
if err != nil {
t.Errorf("%v", err)
continue
}
m := make([]*machineMessage, 0)
err = json.NewDecoder(r.Body).Decode(&m)
r.Body.Close()
if err != nil {
t.Errorf("%v", err)
continue
}
sm := machineSlice(m)
sw := machineSlice(w)
sort.Sort(sm)
sort.Sort(sw)
if !reflect.DeepEqual(sm, sw) {
t.Errorf("on %d: machines = %+v, want %+v", i, sm, sw)
}
}
cl.Destroy()
}
// int64Slice implements sort interface
type machineSlice []*machineMessage
func (s machineSlice) Len() int { return len(s) }
func (s machineSlice) Less(i, j int) bool { return s[i].Name < s[j].Name }
func (s machineSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] }

View File

@ -1,146 +0,0 @@
/*
Copyright 2014 CoreOS Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package etcdserver
import (
"encoding/json"
"fmt"
"net/http"
"strconv"
etcdErr "github.com/coreos/etcd/error"
)
func (p *participant) GetHandler(w http.ResponseWriter, req *http.Request) error {
if req.FormValue("consistent") == "true" && !p.node.IsLeader() {
return p.redirect(w, req, p.node.Leader())
}
key := req.URL.Path[len("/v2/keys"):]
recursive := (req.FormValue("recursive") == "true")
sort := (req.FormValue("sorted") == "true")
waitIndex := req.FormValue("waitIndex")
stream := (req.FormValue("stream") == "true")
if req.FormValue("quorum") == "true" {
return p.handleQuorumGet(key, recursive, sort, w, req)
}
if req.FormValue("wait") == "true" {
return p.handleWatch(key, recursive, stream, waitIndex, w, req)
}
return p.handleGet(key, recursive, sort, w, req)
}
func (p *participant) handleWatch(key string, recursive, stream bool, waitIndex string, w http.ResponseWriter, req *http.Request) error {
// Create a command to watch from a given index (default 0).
var sinceIndex uint64 = 0
var err error
if waitIndex != "" {
sinceIndex, err = strconv.ParseUint(waitIndex, 10, 64)
if err != nil {
return etcdErr.NewError(etcdErr.EcodeIndexNaN, "Watch From Index", p.Store.Index())
}
}
watcher, err := p.Store.Watch(key, recursive, stream, sinceIndex)
if err != nil {
return err
}
cn, _ := w.(http.CloseNotifier)
closeChan := cn.CloseNotify()
p.writeHeaders(w)
w.(http.Flusher).Flush()
if stream {
// watcher hub will not help to remove stream watcher
// so we need to remove here
defer watcher.Remove()
for {
select {
case <-closeChan:
return nil
case event, ok := <-watcher.EventChan:
if !ok {
// If the channel is closed this may be an indication of
// that notifications are much more than we are able to
// send to the client in time. Then we simply end streaming.
return nil
}
if req.Method == "HEAD" {
continue
}
b, _ := json.Marshal(event)
_, err := w.Write(b)
if err != nil {
return nil
}
w.(http.Flusher).Flush()
}
}
}
select {
case <-closeChan:
watcher.Remove()
case event := <-watcher.EventChan:
if req.Method == "HEAD" {
return nil
}
b, _ := json.Marshal(event)
w.Write(b)
}
return nil
}
func (p *participant) handleGet(key string, recursive, sort bool, w http.ResponseWriter, req *http.Request) error {
event, err := p.Store.Get(key, recursive, sort)
if err != nil {
return err
}
p.writeHeaders(w)
if req.Method == "HEAD" {
return nil
}
b, err := json.Marshal(event)
if err != nil {
panic(fmt.Sprintf("handleGet: ", err))
}
w.Write(b)
return nil
}
func (p *participant) handleQuorumGet(key string, recursive, sort bool, w http.ResponseWriter, req *http.Request) error {
if req.Method == "HEAD" {
return fmt.Errorf("not support HEAD")
}
event, err := p.QuorumGet(key, recursive, sort)
if err != nil {
return err
}
p.handleRet(w, event)
return nil
}
func (p *participant) writeHeaders(w http.ResponseWriter) {
w.Header().Set("Content-Type", "application/json")
w.Header().Add("X-Etcd-Index", fmt.Sprint(p.Store.Index()))
// TODO(xiangli): raft-index and term
w.WriteHeader(http.StatusOK)
}

File diff suppressed because it is too large Load Diff

View File

@ -1,46 +0,0 @@
/*
Copyright 2014 CoreOS Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package etcdserver
import (
"net/http"
etcdErr "github.com/coreos/etcd/error"
"github.com/coreos/etcd/store"
)
func (p *participant) PostHandler(w http.ResponseWriter, req *http.Request) error {
if !p.node.IsLeader() {
return p.redirect(w, req, p.node.Leader())
}
key := req.URL.Path[len("/v2/keys"):]
value := req.FormValue("value")
dir := (req.FormValue("dir") == "true")
expireTime, err := store.TTL(req.FormValue("ttl"))
if err != nil {
return etcdErr.NewError(etcdErr.EcodeTTLNaN, "Create", p.Store.Index())
}
ret, err := p.Create(key, dir, value, expireTime, true)
if err == nil {
p.handleRet(w, ret)
return nil
}
return err
}

View File

@ -1,157 +0,0 @@
/*
Copyright 2014 CoreOS Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package etcdserver
import (
"encoding/json"
"fmt"
"net/http"
"net/url"
"strconv"
"time"
etcdErr "github.com/coreos/etcd/error"
"github.com/coreos/etcd/store"
)
func (p *participant) PutHandler(w http.ResponseWriter, req *http.Request) error {
if !p.node.IsLeader() {
return p.redirect(w, req, p.node.Leader())
}
key := req.URL.Path[len("/v2/keys"):]
req.ParseForm()
value := req.Form.Get("value")
dir := (req.FormValue("dir") == "true")
expireTime, err := store.TTL(req.Form.Get("ttl"))
if err != nil {
return etcdErr.NewError(etcdErr.EcodeTTLNaN, "Update", p.Store.Index())
}
prevValue, valueOk := firstValue(req.Form, "prevValue")
prevIndexStr, indexOk := firstValue(req.Form, "prevIndex")
prevExist, existOk := firstValue(req.Form, "prevExist")
// Set handler: create a new node or replace the old one.
if !valueOk && !indexOk && !existOk {
return p.serveSet(w, req, key, dir, value, expireTime)
}
// update with test
if existOk {
if prevExist == "false" {
// Create command: create a new node. Fail, if a node already exists
// Ignore prevIndex and prevValue
return p.serveCreate(w, req, key, dir, value, expireTime)
}
if prevExist == "true" && !indexOk && !valueOk {
return p.serveUpdate(w, req, key, value, expireTime)
}
}
var prevIndex uint64
if indexOk {
prevIndex, err = strconv.ParseUint(prevIndexStr, 10, 64)
// bad previous index
if err != nil {
return etcdErr.NewError(etcdErr.EcodeIndexNaN, "CompareAndSwap", p.Store.Index())
}
} else {
prevIndex = 0
}
if valueOk {
if prevValue == "" {
return etcdErr.NewError(etcdErr.EcodePrevValueRequired, "CompareAndSwap", p.Store.Index())
}
}
return p.serveCAS(w, req, key, value, prevValue, prevIndex, expireTime)
}
func (p *participant) handleRet(w http.ResponseWriter, ret *store.Event) {
b, _ := json.Marshal(ret)
w.Header().Set("Content-Type", "application/json")
// etcd index should be the same as the event index
// which is also the last modified index of the node
w.Header().Add("X-Etcd-Index", fmt.Sprint(ret.Index()))
// w.Header().Add("X-Raft-Index", fmt.Sprint(p.CommitIndex()))
// w.Header().Add("X-Raft-Term", fmt.Sprint(p.Term()))
if ret.IsCreated() {
w.WriteHeader(http.StatusCreated)
} else {
w.WriteHeader(http.StatusOK)
}
w.Write(b)
}
func (p *participant) serveSet(w http.ResponseWriter, req *http.Request, key string, dir bool, value string, expireTime time.Time) error {
ret, err := p.Set(key, dir, value, expireTime)
if err == nil {
p.handleRet(w, ret)
return nil
}
return err
}
func (p *participant) serveCreate(w http.ResponseWriter, req *http.Request, key string, dir bool, value string, expireTime time.Time) error {
ret, err := p.Create(key, dir, value, expireTime, false)
if err == nil {
p.handleRet(w, ret)
return nil
}
return err
}
func (p *participant) serveUpdate(w http.ResponseWriter, req *http.Request, key, value string, expireTime time.Time) error {
// Update should give at least one option
if value == "" && expireTime.Sub(store.Permanent) == 0 {
return etcdErr.NewError(etcdErr.EcodeValueOrTTLRequired, "Update", p.Store.Index())
}
ret, err := p.Update(key, value, expireTime)
if err == nil {
p.handleRet(w, ret)
return nil
}
return err
}
func (p *participant) serveCAS(w http.ResponseWriter, req *http.Request, key, value, prevValue string, prevIndex uint64, expireTime time.Time) error {
ret, err := p.CAS(key, value, prevValue, prevIndex, expireTime)
if err == nil {
p.handleRet(w, ret)
return nil
}
return err
}
func firstValue(f url.Values, key string) (string, bool) {
l, ok := f[key]
if !ok {
return "", false
}
return l[0], true
}

View File

@ -1,63 +0,0 @@
/*
Copyright 2014 CoreOS Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package etcdserver
import (
"fmt"
"time"
"github.com/coreos/etcd/raft"
)
type v2Proposal struct {
data []byte
ret chan interface{}
}
type wait struct {
index int64
term int64
}
type v2Raft struct {
*raft.Node
result map[wait]chan interface{}
term int64
}
func (r *v2Raft) Propose(p v2Proposal) {
if !r.Node.IsLeader() {
p.ret <- fmt.Errorf("not leader")
return
}
r.Node.Propose(p.data)
r.result[wait{r.Index(), r.Term()}] = p.ret
return
}
func (r *v2Raft) Sync() {
if !r.Node.IsLeader() {
return
}
t := time.Now()
sync := &Cmd{Type: stsync, Time: mustMarshalTime(&t)}
data, err := sync.Marshal()
if err != nil {
panic(err)
}
r.Node.Propose(data)
}

View File

@ -1,103 +0,0 @@
/*
Copyright 2014 CoreOS Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package etcdserver
import (
"fmt"
"time"
"github.com/coreos/etcd/store"
)
const (
stset = iota
stcreate
stupdate
stcas
stdelete
stcad
stqget
stsync
)
func (p *participant) Set(key string, dir bool, value string, expireTime time.Time) (*store.Event, error) {
set := &Cmd{Type: stset, Key: key, Dir: &dir, Value: &value, Time: mustMarshalTime(&expireTime)}
return p.do(set)
}
func (p *participant) Create(key string, dir bool, value string, expireTime time.Time, unique bool) (*store.Event, error) {
create := &Cmd{Type: stcreate, Key: key, Dir: &dir, Value: &value, Time: mustMarshalTime(&expireTime), Unique: &unique}
return p.do(create)
}
func (p *participant) Update(key string, value string, expireTime time.Time) (*store.Event, error) {
update := &Cmd{Type: stupdate, Key: key, Value: &value, Time: mustMarshalTime(&expireTime)}
return p.do(update)
}
func (p *participant) CAS(key, value, prevValue string, prevIndex uint64, expireTime time.Time) (*store.Event, error) {
cas := &Cmd{Type: stcas, Key: key, Value: &value, PrevValue: &prevValue, PrevIndex: &prevIndex, Time: mustMarshalTime(&expireTime)}
return p.do(cas)
}
func (p *participant) Delete(key string, dir, recursive bool) (*store.Event, error) {
d := &Cmd{Type: stdelete, Key: key, Dir: &dir, Recursive: &recursive}
return p.do(d)
}
func (p *participant) CAD(key string, prevValue string, prevIndex uint64) (*store.Event, error) {
cad := &Cmd{Type: stcad, Key: key, PrevValue: &prevValue, PrevIndex: &prevIndex}
return p.do(cad)
}
func (p *participant) QuorumGet(key string, recursive, sorted bool) (*store.Event, error) {
get := &Cmd{Type: stqget, Key: key, Recursive: &recursive, Sorted: &sorted}
return p.do(get)
}
func (p *participant) do(c *Cmd) (*store.Event, error) {
data, err := c.Marshal()
if err != nil {
panic(err)
}
pp := v2Proposal{
data: data,
ret: make(chan interface{}, 1),
}
select {
case p.proposal <- pp:
default:
return nil, fmt.Errorf("unable to send out the proposal")
}
var ret interface{}
select {
case ret = <-pp.ret:
case <-p.stopNotifyc:
return nil, fmt.Errorf("stop serving")
}
switch t := ret.(type) {
case *store.Event:
return t, nil
case error:
return nil, t
default:
panic("server.do: unexpected return type")
}
}

View File

@ -1,454 +0,0 @@
// Code generated by protoc-gen-gogo.
// source: v2_store_cmd.proto
// DO NOT EDIT!
/*
Package etcd is a generated protocol buffer package.
It is generated from these files:
v2_store_cmd.proto
It has these top-level messages:
Cmd
*/
package etcdserver
import proto "code.google.com/p/gogoprotobuf/proto"
import json "encoding/json"
import math "math"
// discarding unused import gogoproto "code.google.com/p/gogoprotobuf/gogoproto/gogo.pb"
import io "io"
import code_google_com_p_gogoprotobuf_proto "code.google.com/p/gogoprotobuf/proto"
// Reference proto, json, and math imports to suppress error if they are not otherwise used.
var _ = proto.Marshal
var _ = &json.SyntaxError{}
var _ = math.Inf
type Cmd struct {
Type int32 `protobuf:"varint,1,req,name=type" json:"type"`
Key string `protobuf:"bytes,2,req,name=key" json:"key"`
Value *string `protobuf:"bytes,3,opt,name=value" json:"value,omitempty"`
PrevValue *string `protobuf:"bytes,4,opt,name=prevValue" json:"prevValue,omitempty"`
PrevIndex *uint64 `protobuf:"varint,5,opt,name=prevIndex" json:"prevIndex,omitempty"`
Dir *bool `protobuf:"varint,6,opt,name=dir" json:"dir,omitempty"`
Recursive *bool `protobuf:"varint,7,opt,name=recursive" json:"recursive,omitempty"`
Unique *bool `protobuf:"varint,8,opt,name=unique" json:"unique,omitempty"`
Sorted *bool `protobuf:"varint,9,opt,name=sorted" json:"sorted,omitempty"`
Time []byte `protobuf:"bytes,10,opt,name=time" json:"time,omitempty"`
XXX_unrecognized []byte `json:"-"`
}
func (m *Cmd) Reset() { *m = Cmd{} }
func (m *Cmd) String() string { return proto.CompactTextString(m) }
func (*Cmd) ProtoMessage() {}
func init() {
}
func (m *Cmd) Unmarshal(data []byte) error {
l := len(data)
index := 0
for index < l {
var wire uint64
for shift := uint(0); ; shift += 7 {
if index >= l {
return io.ErrUnexpectedEOF
}
b := data[index]
index++
wire |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
fieldNum := int32(wire >> 3)
wireType := int(wire & 0x7)
switch fieldNum {
case 1:
if wireType != 0 {
return code_google_com_p_gogoprotobuf_proto.ErrWrongType
}
for shift := uint(0); ; shift += 7 {
if index >= l {
return io.ErrUnexpectedEOF
}
b := data[index]
index++
m.Type |= (int32(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
case 2:
if wireType != 2 {
return code_google_com_p_gogoprotobuf_proto.ErrWrongType
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if index >= l {
return io.ErrUnexpectedEOF
}
b := data[index]
index++
stringLen |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
postIndex := index + int(stringLen)
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Key = string(data[index:postIndex])
index = postIndex
case 3:
if wireType != 2 {
return code_google_com_p_gogoprotobuf_proto.ErrWrongType
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if index >= l {
return io.ErrUnexpectedEOF
}
b := data[index]
index++
stringLen |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
postIndex := index + int(stringLen)
if postIndex > l {
return io.ErrUnexpectedEOF
}
s := string(data[index:postIndex])
m.Value = &s
index = postIndex
case 4:
if wireType != 2 {
return code_google_com_p_gogoprotobuf_proto.ErrWrongType
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if index >= l {
return io.ErrUnexpectedEOF
}
b := data[index]
index++
stringLen |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
postIndex := index + int(stringLen)
if postIndex > l {
return io.ErrUnexpectedEOF
}
s := string(data[index:postIndex])
m.PrevValue = &s
index = postIndex
case 5:
if wireType != 0 {
return code_google_com_p_gogoprotobuf_proto.ErrWrongType
}
var v uint64
for shift := uint(0); ; shift += 7 {
if index >= l {
return io.ErrUnexpectedEOF
}
b := data[index]
index++
v |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
m.PrevIndex = &v
case 6:
if wireType != 0 {
return code_google_com_p_gogoprotobuf_proto.ErrWrongType
}
var v int
for shift := uint(0); ; shift += 7 {
if index >= l {
return io.ErrUnexpectedEOF
}
b := data[index]
index++
v |= (int(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
b := bool(v != 0)
m.Dir = &b
case 7:
if wireType != 0 {
return code_google_com_p_gogoprotobuf_proto.ErrWrongType
}
var v int
for shift := uint(0); ; shift += 7 {
if index >= l {
return io.ErrUnexpectedEOF
}
b := data[index]
index++
v |= (int(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
b := bool(v != 0)
m.Recursive = &b
case 8:
if wireType != 0 {
return code_google_com_p_gogoprotobuf_proto.ErrWrongType
}
var v int
for shift := uint(0); ; shift += 7 {
if index >= l {
return io.ErrUnexpectedEOF
}
b := data[index]
index++
v |= (int(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
b := bool(v != 0)
m.Unique = &b
case 9:
if wireType != 0 {
return code_google_com_p_gogoprotobuf_proto.ErrWrongType
}
var v int
for shift := uint(0); ; shift += 7 {
if index >= l {
return io.ErrUnexpectedEOF
}
b := data[index]
index++
v |= (int(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
b := bool(v != 0)
m.Sorted = &b
case 10:
if wireType != 2 {
return code_google_com_p_gogoprotobuf_proto.ErrWrongType
}
var byteLen int
for shift := uint(0); ; shift += 7 {
if index >= l {
return io.ErrUnexpectedEOF
}
b := data[index]
index++
byteLen |= (int(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
postIndex := index + byteLen
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Time = append(m.Time, data[index:postIndex]...)
index = postIndex
default:
var sizeOfWire int
for {
sizeOfWire++
wire >>= 7
if wire == 0 {
break
}
}
index -= sizeOfWire
skippy, err := code_google_com_p_gogoprotobuf_proto.Skip(data[index:])
if err != nil {
return err
}
if (index + skippy) > l {
return io.ErrUnexpectedEOF
}
m.XXX_unrecognized = append(m.XXX_unrecognized, data[index:index+skippy]...)
index += skippy
}
}
return nil
}
func (m *Cmd) Size() (n int) {
var l int
_ = l
n += 1 + sovV2StoreCmd(uint64(uint32(m.Type)))
l = len(m.Key)
n += 1 + l + sovV2StoreCmd(uint64(l))
if m.Value != nil {
l = len(*m.Value)
n += 1 + l + sovV2StoreCmd(uint64(l))
}
if m.PrevValue != nil {
l = len(*m.PrevValue)
n += 1 + l + sovV2StoreCmd(uint64(l))
}
if m.PrevIndex != nil {
n += 1 + sovV2StoreCmd(uint64(*m.PrevIndex))
}
if m.Dir != nil {
n += 2
}
if m.Recursive != nil {
n += 2
}
if m.Unique != nil {
n += 2
}
if m.Sorted != nil {
n += 2
}
if m.Time != nil {
l = len(m.Time)
n += 1 + l + sovV2StoreCmd(uint64(l))
}
if m.XXX_unrecognized != nil {
n += len(m.XXX_unrecognized)
}
return n
}
func sovV2StoreCmd(x uint64) (n int) {
for {
n++
x >>= 7
if x == 0 {
break
}
}
return n
}
func sozV2StoreCmd(x uint64) (n int) {
return sovV2StoreCmd(uint64((x << 1) ^ uint64((int64(x) >> 63))))
}
func (m *Cmd) Marshal() (data []byte, err error) {
size := m.Size()
data = make([]byte, size)
n, err := m.MarshalTo(data)
if err != nil {
return nil, err
}
return data[:n], nil
}
func (m *Cmd) MarshalTo(data []byte) (n int, err error) {
var i int
_ = i
var l int
_ = l
data[i] = 0x8
i++
i = encodeVarintV2StoreCmd(data, i, uint64(uint32(m.Type)))
data[i] = 0x12
i++
i = encodeVarintV2StoreCmd(data, i, uint64(len(m.Key)))
i += copy(data[i:], m.Key)
if m.Value != nil {
data[i] = 0x1a
i++
i = encodeVarintV2StoreCmd(data, i, uint64(len(*m.Value)))
i += copy(data[i:], *m.Value)
}
if m.PrevValue != nil {
data[i] = 0x22
i++
i = encodeVarintV2StoreCmd(data, i, uint64(len(*m.PrevValue)))
i += copy(data[i:], *m.PrevValue)
}
if m.PrevIndex != nil {
data[i] = 0x28
i++
i = encodeVarintV2StoreCmd(data, i, uint64(*m.PrevIndex))
}
if m.Dir != nil {
data[i] = 0x30
i++
if *m.Dir {
data[i] = 1
} else {
data[i] = 0
}
i++
}
if m.Recursive != nil {
data[i] = 0x38
i++
if *m.Recursive {
data[i] = 1
} else {
data[i] = 0
}
i++
}
if m.Unique != nil {
data[i] = 0x40
i++
if *m.Unique {
data[i] = 1
} else {
data[i] = 0
}
i++
}
if m.Sorted != nil {
data[i] = 0x48
i++
if *m.Sorted {
data[i] = 1
} else {
data[i] = 0
}
i++
}
if m.Time != nil {
data[i] = 0x52
i++
i = encodeVarintV2StoreCmd(data, i, uint64(len(m.Time)))
i += copy(data[i:], m.Time)
}
if m.XXX_unrecognized != nil {
i += copy(data[i:], m.XXX_unrecognized)
}
return i, nil
}
func encodeFixed64V2StoreCmd(data []byte, offset int, v uint64) int {
data[offset] = uint8(v)
data[offset+1] = uint8(v >> 8)
data[offset+2] = uint8(v >> 16)
data[offset+3] = uint8(v >> 24)
data[offset+4] = uint8(v >> 32)
data[offset+5] = uint8(v >> 40)
data[offset+6] = uint8(v >> 48)
data[offset+7] = uint8(v >> 56)
return offset + 8
}
func encodeFixed32V2StoreCmd(data []byte, offset int, v uint32) int {
data[offset] = uint8(v)
data[offset+1] = uint8(v >> 8)
data[offset+2] = uint8(v >> 16)
data[offset+3] = uint8(v >> 24)
return offset + 4
}
func encodeVarintV2StoreCmd(data []byte, offset int, v uint64) int {
for v >= 1<<7 {
data[offset] = uint8(v&0x7f | 0x80)
v >>= 7
offset++
}
data[offset] = uint8(v)
return offset + 1
}

View File

@ -1,21 +0,0 @@
package etcdserver;
import "code.google.com/p/gogoprotobuf/gogoproto/gogo.proto";
option (gogoproto.marshaler_all) = true;
option (gogoproto.sizer_all) = true;
option (gogoproto.unmarshaler_all) = true;
option (gogoproto.goproto_getters_all) = false;
message cmd {
required int32 type = 1 [(gogoproto.nullable) = false];
required string key = 2 [(gogoproto.nullable) = false];
optional string value = 3;
optional string prevValue = 4;
optional uint64 prevIndex = 5;
optional bool dir = 6;
optional bool recursive = 7;
optional bool unique = 8;
optional bool sorted = 9;
optional bytes time = 10;
}

View File

@ -1,82 +0,0 @@
/*
Copyright 2014 CoreOS Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package etcdserver
import (
"strings"
)
// usage defines the message shown when a help flag is passed to etcd.
var usage = `
etcd
Usage:
etcd -name <name>
etcd -name <name> [-data-dir=<path>]
etcd -h | -help
etcd -version
Options:
-h -help Show this screen.
--version Show version.
-f -force Force a new configuration to be used.
-config=<path> Path to configuration file.
-name=<name> Name of this node in the etcd cluster.
-data-dir=<path> Path to the data directory.
-cors=<origins> Comma-separated list of CORS origins.
-v Enabled verbose logging.
-vv Enabled very verbose logging.
Cluster Configuration Options:
-discovery=<url> Discovery service used to find a peer list.
-peers-file=<path> Path to a file containing the peer list.
-peers=<host:port>,<host:port> Comma-separated list of peers. The members
should match the peer's '-peer-addr' flag.
Client Communication Options:
-addr=<host:port> The public host:port used for client communication.
-bind-addr=<host[:port]> The listening host:port used for client communication.
-ca-file=<path> Path to the client CA file.
-cert-file=<path> Path to the client cert file.
-key-file=<path> Path to the client key file.
Peer Communication Options:
-peer-addr=<host:port> The public host:port used for peer communication.
-peer-bind-addr=<host[:port]> The listening host:port used for peer communication.
-peer-ca-file=<path> Path to the peer CA file.
-peer-cert-file=<path> Path to the peer cert file.
-peer-key-file=<path> Path to the peer key file.
-peer-heartbeat-interval=<time>
Time (in milliseconds) of a heartbeat interval.
-peer-election-timeout=<time>
Time (in milliseconds) for an election to timeout.
Other Options:
-max-result-buffer Max size of the result buffer.
-max-retry-attempts Number of times a node will try to join a cluster.
-retry-interval Seconds to wait between cluster join retry attempts.
-snapshot=false Disable log snapshots
-snapshot-count Number of transactions before issuing a snapshot.
-cluster-active-size Number of active nodes in the cluster.
-cluster-remove-delay Seconds before one node is removed.
-cluster-sync-interval Seconds between synchronizations for standby mode.
`
// Usage returns the usage message for etcd.
func Usage() string {
return strings.TrimSpace(usage)
}

View File

@ -1,14 +0,0 @@
package etcdserver
import (
"fmt"
"net/http"
)
const (
releaseVersion = "0.5rc1+git"
)
func versionHandler(w http.ResponseWriter, req *http.Request) {
fmt.Fprintf(w, "etcd %s", releaseVersion)
}

View File

@ -1,94 +0,0 @@
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package etcdserver
import (
"net/http"
"runtime"
"sort"
"strings"
"testing"
"time"
)
func interestingGoroutines() (gs []string) {
buf := make([]byte, 2<<20)
buf = buf[:runtime.Stack(buf, true)]
for _, g := range strings.Split(string(buf), "\n\n") {
sl := strings.SplitN(g, "\n", 2)
if len(sl) != 2 {
continue
}
stack := strings.TrimSpace(sl[1])
if stack == "" ||
strings.Contains(stack, "created by testing.RunTests") ||
strings.Contains(stack, "testing.Main(") ||
strings.Contains(stack, "runtime.goexit") ||
strings.Contains(stack, "created by runtime.gc") ||
strings.Contains(stack, "runtime.MHeap_Scavenger") {
continue
}
gs = append(gs, stack)
}
sort.Strings(gs)
return
}
// Verify the other tests didn't leave any goroutines running.
// This is in a file named z_last_test.go so it sorts at the end.
func TestGoroutinesRunning(t *testing.T) {
if testing.Short() {
t.Skip("not counting goroutines for leakage in -short mode")
}
gs := interestingGoroutines()
n := 0
stackCount := make(map[string]int)
for _, g := range gs {
stackCount[g]++
n++
}
t.Logf("num goroutines = %d", n)
if n > 0 {
t.Error("Too many goroutines.")
for stack, count := range stackCount {
t.Logf("%d instances of:\n%s", count, stack)
}
}
}
func afterTest(t *testing.T) {
http.DefaultTransport.(*http.Transport).CloseIdleConnections()
if testing.Short() {
return
}
var bad string
badSubstring := map[string]string{
").readLoop(": "a Transport",
").writeLoop(": "a Transport",
"created by net/http/httptest.(*Server).Start": "an httptest.Server",
"timeoutHandler": "a TimeoutHandler",
"net.(*netFD).connect(": "a timing out dial",
").noteClientGone(": "a closenotifier sender",
}
var stacks string
for i := 0; i < 6; i++ {
bad = ""
stacks = strings.Join(interestingGoroutines(), "\n\n")
for substr, what := range badSubstring {
if strings.Contains(stacks, substr) {
bad = what
}
}
if bad == "" {
return
}
// Bad stuff found, but goroutines might just still be
// shutting down, so give it some time.
time.Sleep(50 * time.Millisecond)
}
t.Errorf("Test appears to have leaked %s:\n%s", bad, stacks)
}

View File

@ -7,8 +7,8 @@ import (
"strconv" "strconv"
"time" "time"
etcdserver "github.com/coreos/etcd/etcdserver2" "github.com/coreos/etcd/etcdserver"
"github.com/coreos/etcd/etcdserver2/etcdhttp" "github.com/coreos/etcd/etcdserver/etcdhttp"
"github.com/coreos/etcd/raft" "github.com/coreos/etcd/raft"
"github.com/coreos/etcd/raft/raftpb" "github.com/coreos/etcd/raft/raftpb"
"github.com/coreos/etcd/store" "github.com/coreos/etcd/store"

View File

@ -1,93 +0,0 @@
// +build ignore
package test
import (
"errors"
"fmt"
"net/http"
"net/http/httptest"
"net/url"
"strings"
"sync"
"testing"
"time"
"github.com/coreos/etcd/third_party/github.com/stretchr/testify/assert"
"github.com/coreos/etcd/server"
etcdtest "github.com/coreos/etcd/tests"
goetcd "github.com/coreos/etcd/third_party/github.com/coreos/go-etcd/etcd"
)
type garbageHandler struct {
t *testing.T
success bool
sync.Mutex
}
func (g *garbageHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Hello, client")
if r.URL.String() != "/v2/keys/_etcd/registry/1/node1" {
g.t.Fatalf("Unexpected web request")
}
g.Lock()
defer g.Unlock()
g.success = true
}
// TestDiscoverySecondPeerFirstNoResponse ensures that if the first etcd
// machine stops after heartbeating that the second machine fails too.
func TestDiscoverySecondPeerFirstNoResponse(t *testing.T) {
etcdtest.RunServer(func(s *server.Server) {
v := url.Values{}
v.Set("value", "started")
resp, err := etcdtest.PutForm(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/_etcd/registry/2/_state"), v)
assert.Equal(t, resp.StatusCode, http.StatusCreated)
v = url.Values{}
v.Set("value", "http://127.0.0.1:49151")
resp, err = etcdtest.PutForm(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/_etcd/registry/2/ETCDTEST"), v)
assert.Equal(t, resp.StatusCode, http.StatusCreated)
proc, err := startServer([]string{"-retry-interval", "0.2", "-discovery", s.URL() + "/v2/keys/_etcd/registry/2"})
if err != nil {
t.Fatal(err.Error())
}
defer stopServer(proc)
// TODO(bp): etcd will take 30 seconds to shutdown, figure this
// out instead
time.Sleep(1 * time.Second)
client := http.Client{}
_, err = client.Get("/")
if err != nil && strings.Contains(err.Error(), "connection reset by peer") {
t.Fatal(err.Error())
}
})
}
func assertServerNotUp(client http.Client, scheme string) error {
path := fmt.Sprintf("%s://127.0.0.1:4001/v2/keys/foo", scheme)
fields := url.Values(map[string][]string{"value": {"bar"}})
for i := 0; i < 10; i++ {
time.Sleep(1 * time.Second)
_, err := client.PostForm(path, fields)
if err == nil {
return errors.New("Expected error during POST, got nil")
} else {
errString := err.Error()
if strings.Contains(errString, "connection refused") {
return nil
} else {
return err
}
}
}
return nil
}

View File

@ -1,36 +0,0 @@
// +build ignore
package test
import (
"net/http"
"os"
"testing"
"time"
)
func BenchmarkEtcdDirectCall(b *testing.B) {
templateBenchmarkEtcdDirectCall(b, false)
}
func BenchmarkEtcdDirectCallTls(b *testing.B) {
templateBenchmarkEtcdDirectCall(b, true)
}
func templateBenchmarkEtcdDirectCall(b *testing.B, tls bool) {
procAttr := new(os.ProcAttr)
procAttr.Files = []*os.File{nil, os.Stdout, os.Stderr}
clusterSize := 3
_, etcds, _ := CreateCluster(clusterSize, procAttr, tls)
defer DestroyCluster(etcds)
time.Sleep(time.Second)
b.ResetTimer()
for i := 0; i < b.N; i++ {
resp, _ := http.Get("http://127.0.0.1:4001/test/speed")
resp.Body.Close()
}
}

View File

@ -1,270 +0,0 @@
// +build ignore
package test
import (
"crypto/tls"
"crypto/x509"
"errors"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"os"
"strings"
"testing"
"time"
)
// TestTLSOff asserts that non-TLS-encrypted communication between the
// etcd server and an unauthenticated client works
func TestTLSOff(t *testing.T) {
proc, err := startServer([]string{})
if err != nil {
t.Fatal(err.Error())
}
defer stopServer(proc)
client := buildClient()
err = assertServerFunctional(client, "http")
if err != nil {
t.Fatal(err.Error())
}
}
// TestTLSAnonymousClient asserts that TLS-encrypted communication between the etcd
// server and an anonymous client works
func TestTLSAnonymousClient(t *testing.T) {
proc, err := startServer([]string{
"-cert-file=../../fixtures/ca/server.crt",
"-key-file=../../fixtures/ca/server.key.insecure",
})
if err != nil {
t.Fatal(err.Error())
}
defer stopServer(proc)
cacertfile := "../../fixtures/ca/ca.crt"
cp := x509.NewCertPool()
bytes, err := ioutil.ReadFile(cacertfile)
if err != nil {
panic(err)
}
cp.AppendCertsFromPEM(bytes)
cfg := tls.Config{}
cfg.RootCAs = cp
client := buildTLSClient(&cfg)
err = assertServerFunctional(client, "https")
if err != nil {
t.Fatal(err)
}
}
// TestTLSAuthenticatedClient asserts that TLS-encrypted communication
// between the etcd server and an authenticated client works
func TestTLSAuthenticatedClient(t *testing.T) {
proc, err := startServer([]string{
"-cert-file=../../fixtures/ca/server.crt",
"-key-file=../../fixtures/ca/server.key.insecure",
"-ca-file=../../fixtures/ca/ca.crt",
})
if err != nil {
t.Fatal(err.Error())
}
defer stopServer(proc)
cacertfile := "../../fixtures/ca/ca.crt"
certfile := "../../fixtures/ca/server2.crt"
keyfile := "../../fixtures/ca/server2.key.insecure"
cert, err := tls.LoadX509KeyPair(certfile, keyfile)
if err != nil {
panic(err)
}
cp := x509.NewCertPool()
bytes, err := ioutil.ReadFile(cacertfile)
if err != nil {
panic(err)
}
cp.AppendCertsFromPEM(bytes)
cfg := tls.Config{}
cfg.Certificates = []tls.Certificate{cert}
cfg.RootCAs = cp
time.Sleep(time.Second)
client := buildTLSClient(&cfg)
err = assertServerFunctional(client, "https")
if err != nil {
t.Fatal(err)
}
}
// TestTLSUnathenticatedClient asserts that TLS-encrypted communication
// between the etcd server and an unauthenticated client fails
func TestTLSUnauthenticatedClient(t *testing.T) {
proc, err := startServer([]string{
"-cert-file=../../fixtures/ca/server.crt",
"-key-file=../../fixtures/ca/server.key.insecure",
"-ca-file=../../fixtures/ca/ca.crt",
})
if err != nil {
t.Fatal(err.Error())
}
defer stopServer(proc)
cacertfile := "../../fixtures/ca/ca.crt"
certfile := "../../fixtures/ca/broken_server.crt"
keyfile := "../../fixtures/ca/broken_server.key.insecure"
cert, err := tls.LoadX509KeyPair(certfile, keyfile)
if err != nil {
panic(err)
}
cp := x509.NewCertPool()
bytes, err := ioutil.ReadFile(cacertfile)
if err != nil {
panic(err)
}
cp.AppendCertsFromPEM(bytes)
cfg := tls.Config{}
cfg.Certificates = []tls.Certificate{cert}
cfg.RootCAs = cp
time.Sleep(time.Second)
client := buildTLSClient(&cfg)
err = assertServerNotFunctional(client, "https")
if err != nil {
t.Fatal(err)
}
}
func buildClient() http.Client {
return http.Client{}
}
func buildTLSClient(tlsConf *tls.Config) http.Client {
tr := http.Transport{TLSClientConfig: tlsConf}
return http.Client{Transport: &tr}
}
func startServer(extra []string) (*os.Process, error) {
procAttr := new(os.ProcAttr)
procAttr.Files = []*os.File{nil, os.Stdout, os.Stderr}
cmd := []string{"etcd", "-f", "-data-dir=/tmp/node1", "-name=node1"}
cmd = append(cmd, extra...)
println(strings.Join(cmd, " "))
return os.StartProcess(EtcdBinPath, cmd, procAttr)
}
// TODO(yichengq): refactor these helper functions in #645
func startServer2(extra []string) (*os.Process, error) {
procAttr := new(os.ProcAttr)
procAttr.Files = []*os.File{nil, os.Stdout, os.Stderr}
cmd := []string{"etcd", "-f", "-data-dir=/tmp/node2", "-name=node2"}
cmd = append(cmd, extra...)
fmt.Println(strings.Join(cmd, " "))
return os.StartProcess(EtcdBinPath, cmd, procAttr)
}
func startServerWithDataDir(extra []string) (*os.Process, error) {
procAttr := new(os.ProcAttr)
procAttr.Files = []*os.File{nil, os.Stdout, os.Stderr}
cmd := []string{"etcd", "-data-dir=/tmp/node1", "-name=node1"}
cmd = append(cmd, extra...)
fmt.Println(strings.Join(cmd, " "))
return os.StartProcess(EtcdBinPath, cmd, procAttr)
}
func startServer2WithDataDir(extra []string) (*os.Process, error) {
procAttr := new(os.ProcAttr)
procAttr.Files = []*os.File{nil, os.Stdout, os.Stderr}
cmd := []string{"etcd", "-data-dir=/tmp/node2", "-name=node2"}
cmd = append(cmd, extra...)
println(strings.Join(cmd, " "))
return os.StartProcess(EtcdBinPath, cmd, procAttr)
}
func stopServer(proc *os.Process) {
err := proc.Kill()
if err != nil {
panic(err.Error())
}
proc.Release()
}
func assertServerFunctional(client http.Client, scheme string) error {
path := fmt.Sprintf("%s://127.0.0.1:4001/v2/keys/foo", scheme)
fields := url.Values(map[string][]string{"value": {"bar"}})
for i := 0; i < 10; i++ {
time.Sleep(1 * time.Second)
resp, err := client.PostForm(path, fields)
// If the status is Temporary Redirect, we should follow the
// new location, because the request did not go to the leader yet.
// TODO(yichengq): the difference between Temporary Redirect(307)
// and Created(201) could distinguish between leader and followers
for err == nil && resp.StatusCode == http.StatusTemporaryRedirect {
loc, _ := resp.Location()
newPath := loc.String()
resp, err = client.PostForm(newPath, fields)
}
if err == nil {
// Internal error may mean that servers are in leader election
if resp.StatusCode != http.StatusCreated && resp.StatusCode != http.StatusInternalServerError {
return errors.New(fmt.Sprintf("resp.StatusCode == %s", resp.Status))
} else {
return nil
}
}
}
return errors.New("etcd server was not reachable in time / had internal error")
}
func assertServerNotFunctional(client http.Client, scheme string) error {
path := fmt.Sprintf("%s://127.0.0.1:4001/v2/keys/foo", scheme)
fields := url.Values(map[string][]string{"value": {"bar"}})
for i := 0; i < 10; i++ {
time.Sleep(1 * time.Second)
_, err := client.PostForm(path, fields)
if err == nil {
return errors.New("Expected error during POST, got nil")
} else {
errString := err.Error()
if strings.Contains(errString, "connection refused") {
continue
} else if strings.Contains(errString, "bad certificate") {
return nil
} else {
return err
}
}
}
return errors.New("Expected server to fail with 'bad certificate'")
}

View File

@ -1,19 +0,0 @@
// +build ignore
package test
import (
"go/build"
"os"
"path/filepath"
)
var EtcdBinPath string
func init() {
// Initialize the 'etcd' binary path or default it to the etcd diretory.
EtcdBinPath = os.Getenv("ETCD_BIN_PATH")
if EtcdBinPath == "" {
EtcdBinPath = filepath.Join(build.Default.GOPATH, "src", "github.com", "coreos", "etcd", "etcd")
}
}

View File

@ -1,65 +0,0 @@
// +build ignore
package test
import (
"fmt"
"net/http"
"net/http/httptest"
"net/url"
"os"
"sync"
"testing"
"time"
)
// Ensure that etcd does not come up if the internal raft versions do not match.
func TestInternalVersion(t *testing.T) {
var mu sync.Mutex
checkedVersion := false
testMux := http.NewServeMux()
testMux.HandleFunc("/version", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "This is not a version number")
mu.Lock()
defer mu.Unlock()
checkedVersion = true
})
testMux.HandleFunc("/join", func(w http.ResponseWriter, r *http.Request) {
t.Fatal("should not attempt to join!")
})
ts := httptest.NewServer(testMux)
defer ts.Close()
fakeURL, _ := url.Parse(ts.URL)
procAttr := new(os.ProcAttr)
procAttr.Files = []*os.File{nil, os.Stdout, os.Stderr}
args := []string{"etcd", "-name=node1", "-f", "-data-dir=/tmp/node1", "-peers=" + fakeURL.Host}
process, err := os.StartProcess(EtcdBinPath, args, procAttr)
if err != nil {
t.Fatal("start process failed:" + err.Error())
return
}
time.Sleep(time.Second)
process.Kill()
_, err = http.Get("http://127.0.0.1:4001")
if err == nil {
t.Fatal("etcd node should not be up")
return
}
mu.Lock()
defer mu.Unlock()
if checkedVersion == false {
t.Fatal("etcd did not check the version")
return
}
}

View File

@ -1,103 +0,0 @@
// +build ignore
package test
import (
"bytes"
"fmt"
"os"
"strconv"
"strings"
"testing"
"time"
"github.com/coreos/etcd/server"
"github.com/coreos/etcd/tests"
"github.com/coreos/etcd/third_party/github.com/coreos/go-etcd/etcd"
"github.com/coreos/etcd/third_party/github.com/stretchr/testify/assert"
)
// This test will kill the current leader and wait for the etcd cluster to elect a new leader for 200 times.
// It will print out the election time and the average election time.
// It runs in a cluster with standby nodes.
func TestKillLeaderWithStandbys(t *testing.T) {
// https://github.com/goraft/raft/issues/222
t.Skip("stuck on raft issue")
procAttr := new(os.ProcAttr)
procAttr.Files = []*os.File{nil, os.Stdout, os.Stderr}
clusterSize := 5
argGroup, etcds, err := CreateCluster(clusterSize, procAttr, false)
if err != nil {
t.Fatal("cannot create cluster")
}
defer DestroyCluster(etcds)
stop := make(chan bool)
leaderChan := make(chan string, 1)
all := make(chan bool, 1)
time.Sleep(time.Second)
go Monitor(clusterSize, 1, leaderChan, all, stop)
c := etcd.NewClient(nil)
c.SyncCluster()
// Reconfigure with a small active size.
resp, _ := tests.Put("http://localhost:7001/v2/admin/config", "application/json", bytes.NewBufferString(`{"activeSize":3, "removeDelay":2, "syncInterval":1}`))
if !assert.Equal(t, resp.StatusCode, 200) {
t.FailNow()
}
// Wait for two monitor cycles before checking for demotion.
time.Sleep((2 * server.ActiveMonitorTimeout) + (2 * time.Second))
// Verify that we have 3 peers.
result, err := c.Get("_etcd/machines", true, true)
assert.NoError(t, err)
assert.Equal(t, len(result.Node.Nodes), 3)
var totalTime time.Duration
leader := "http://127.0.0.1:7001"
for i := 0; i < clusterSize; i++ {
t.Log("leader is ", leader)
port, _ := strconv.Atoi(strings.Split(leader, ":")[2])
num := port - 7001
t.Log("kill server ", num)
etcds[num].Kill()
etcds[num].Release()
start := time.Now()
for {
newLeader := <-leaderChan
if newLeader != leader {
leader = newLeader
break
}
}
take := time.Now().Sub(start)
totalTime += take
avgTime := totalTime / (time.Duration)(i+1)
fmt.Println("Total time:", totalTime, "; Avg time:", avgTime)
time.Sleep(server.ActiveMonitorTimeout + (1 * time.Second))
time.Sleep(2 * time.Second)
// Verify that we have 3 peers.
result, err = c.Get("_etcd/machines", true, true)
assert.NoError(t, err)
assert.Equal(t, len(result.Node.Nodes), 3)
// Verify that killed node is not one of those peers.
_, err = c.Get(fmt.Sprintf("_etcd/machines/node%d", num+1), false, false)
assert.Error(t, err)
etcds[num], err = os.StartProcess(EtcdBinPath, argGroup[num], procAttr)
}
stop <- true
}

View File

@ -1,248 +0,0 @@
// +build ignore
package test
import (
"bytes"
"os"
"strconv"
"strings"
"testing"
"time"
"github.com/coreos/etcd/server"
"github.com/coreos/etcd/tests"
"github.com/coreos/etcd/third_party/github.com/coreos/go-etcd/etcd"
"github.com/coreos/etcd/third_party/github.com/stretchr/testify/assert"
)
// TestTLSMultiNodeKillAllAndRecovery create a five nodes
// then kill all the nodes and restart
func TestTLSMultiNodeKillAllAndRecovery(t *testing.T) {
procAttr := new(os.ProcAttr)
procAttr.Files = []*os.File{nil, os.Stdout, os.Stderr}
stop := make(chan bool)
leaderChan := make(chan string, 1)
all := make(chan bool, 1)
clusterSize := 5
argGroup, etcds, err := CreateCluster(clusterSize, procAttr, true)
defer DestroyCluster(etcds)
if err != nil {
t.Fatal("cannot create cluster")
}
time.Sleep(time.Second)
c := etcd.NewClient(nil)
go Monitor(clusterSize, clusterSize, leaderChan, all, stop)
<-all
<-leaderChan
stop <- true
c.SyncCluster()
// send 10 commands
for i := 0; i < 10; i++ {
// Test Set
_, err := c.Set("foo", "bar", 0)
if err != nil {
panic(err)
}
}
time.Sleep(time.Second)
// kill all
DestroyCluster(etcds)
time.Sleep(time.Second)
stop = make(chan bool)
leaderChan = make(chan string, 1)
all = make(chan bool, 1)
time.Sleep(time.Second)
for i := 0; i < clusterSize; i++ {
etcds[i], err = os.StartProcess(EtcdBinPath, argGroup[i], procAttr)
// See util.go for the reason to wait for server
client := buildClient()
err = WaitForServer("127.0.0.1:400"+strconv.Itoa(i+1), client, "http")
if err != nil {
t.Fatalf("node start error: %s", err)
}
}
go Monitor(clusterSize, 1, leaderChan, all, stop)
<-all
<-leaderChan
result, err := c.Set("foo", "bar", 0)
if err != nil {
t.Fatalf("Recovery error: %s", err)
}
if result.Node.ModifiedIndex != 17 {
t.Fatalf("recovery failed! [%d/17]", result.Node.ModifiedIndex)
}
}
// Create a five-node cluster
// Kill all the nodes and restart
func TestMultiNodeKillAllAndRecoveryWithStandbys(t *testing.T) {
procAttr := new(os.ProcAttr)
procAttr.Files = []*os.File{nil, os.Stdout, os.Stderr}
stop := make(chan bool)
leaderChan := make(chan string, 1)
all := make(chan bool, 1)
clusterSize := 15
argGroup, etcds, err := CreateCluster(clusterSize, procAttr, false)
defer DestroyCluster(etcds)
if err != nil {
t.Fatal("cannot create cluster")
}
c := etcd.NewClient(nil)
go Monitor(clusterSize, clusterSize, leaderChan, all, stop)
<-all
<-leaderChan
stop <- true
c.SyncCluster()
// Reconfigure with smaller active size (7 nodes) and wait for remove.
resp, _ := tests.Put("http://localhost:7001/v2/admin/config", "application/json", bytes.NewBufferString(`{"activeSize":7}`))
if !assert.Equal(t, resp.StatusCode, 200) {
t.FailNow()
}
time.Sleep(2*server.ActiveMonitorTimeout + (1 * time.Second))
// Verify that there is three machines in peer mode.
result, err := c.Get("_etcd/machines", false, true)
assert.NoError(t, err)
assert.Equal(t, len(result.Node.Nodes), 7)
// send set commands
for i := 0; i < 2*clusterSize; i++ {
// Test Set
_, err := c.Set("foo", "bar", 0)
if err != nil {
panic(err)
}
}
time.Sleep(time.Second)
// kill all
DestroyCluster(etcds)
time.Sleep(time.Second)
stop = make(chan bool)
leaderChan = make(chan string, 1)
all = make(chan bool, 1)
time.Sleep(time.Second)
for i := 0; i < clusterSize; i++ {
etcds[i], err = os.StartProcess(EtcdBinPath, append(argGroup[i], "-peers="), procAttr)
}
time.Sleep(2 * time.Second)
// send set commands
for i := 0; i < 2*clusterSize; i++ {
// Test Set
_, err := c.Set("foo", "bar", 0)
if err != nil {
t.Fatalf("Recovery error: %s", err)
}
}
// Verify that we have seven machines.
result, err = c.Get("_etcd/machines", false, true)
assert.NoError(t, err)
assert.Equal(t, len(result.Node.Nodes), 7)
}
// Create a five nodes
// Kill all the nodes and restart, then remove the leader
func TestMultiNodeKillAllAndRecoveryAndRemoveLeader(t *testing.T) {
procAttr := new(os.ProcAttr)
procAttr.Files = []*os.File{nil, os.Stdout, os.Stderr}
stop := make(chan bool)
leaderChan := make(chan string, 1)
all := make(chan bool, 1)
clusterSize := 5
argGroup, etcds, err := CreateCluster(clusterSize, procAttr, false)
defer DestroyCluster(etcds)
if err != nil {
t.Fatal("cannot create cluster")
}
c := etcd.NewClient(nil)
go Monitor(clusterSize, clusterSize, leaderChan, all, stop)
<-all
<-leaderChan
stop <- true
// It needs some time to sync current commits and write it to disk.
// Or some instance may be restarted as a new peer, and we don't support
// to connect back the old cluster that doesn't have majority alive
// without log now.
time.Sleep(time.Second)
c.SyncCluster()
// kill all
DestroyCluster(etcds)
time.Sleep(time.Second)
stop = make(chan bool)
leaderChan = make(chan string, 1)
all = make(chan bool, 1)
time.Sleep(time.Second)
for i := 0; i < clusterSize; i++ {
etcds[i], err = os.StartProcess(EtcdBinPath, argGroup[i], procAttr)
}
go Monitor(clusterSize, 1, leaderChan, all, stop)
<-all
leader := <-leaderChan
_, err = c.Set("foo", "bar", 0)
if err != nil {
t.Fatalf("Recovery error: %s", err)
}
port, _ := strconv.Atoi(strings.Split(leader, ":")[2])
num := port - 7000
resp, _ := tests.Delete(leader+"/v2/admin/machines/node"+strconv.Itoa(num), "application/json", nil)
if !assert.Equal(t, resp.StatusCode, 200) {
t.FailNow()
}
// check the old leader is in standby mode now
time.Sleep(time.Second)
resp, _ = tests.Get(leader + "/name")
assert.Equal(t, resp.StatusCode, 404)
}

View File

@ -1,195 +0,0 @@
// +build ignore
package test
import (
"fmt"
"math/rand"
"os"
"strconv"
"strings"
"testing"
"time"
"github.com/coreos/etcd/third_party/github.com/coreos/go-etcd/etcd"
)
func increasePeerAddressPort(args []string, delta int) []string {
for i, arg := range args {
if !strings.Contains(arg, "peer-addr") {
continue
}
splitArg := strings.Split(arg, ":")
port, _ := strconv.Atoi(splitArg[len(splitArg)-1])
args[i] = "-peer-addr=127.0.0.1:" + strconv.Itoa(port+delta)
return args
}
return append(args, "-peer-addr=127.0.0.1:"+strconv.Itoa(7001+delta))
}
func increaseAddressPort(args []string, delta int) []string {
for i, arg := range args {
if !strings.HasPrefix(arg, "-addr") && !strings.HasPrefix(arg, "--addr") {
continue
}
splitArg := strings.Split(arg, ":")
port, _ := strconv.Atoi(splitArg[len(splitArg)-1])
args[i] = "-addr=127.0.0.1:" + strconv.Itoa(port+delta)
return args
}
return append(args, "-addr=127.0.0.1:"+strconv.Itoa(4001+delta))
}
func increaseDataDir(args []string, delta int) []string {
for i, arg := range args {
if !strings.Contains(arg, "-data-dir") {
continue
}
splitArg := strings.Split(arg, "node")
idx, _ := strconv.Atoi(splitArg[len(splitArg)-1])
args[i] = "-data-dir=/tmp/node" + strconv.Itoa(idx+delta)
return args
}
return args
}
// Create a five-node cluster
// Random kill one of the nodes and restart it with different peer address
func TestRejoinWithDifferentPeerAddress(t *testing.T) {
procAttr := new(os.ProcAttr)
procAttr.Files = []*os.File{nil, os.Stdout, os.Stderr}
clusterSize := 5
argGroup, etcds, err := CreateCluster(clusterSize, procAttr, false)
if err != nil {
t.Fatal("cannot create cluster")
}
defer DestroyCluster(etcds)
time.Sleep(2 * time.Second)
for i := 0; i < 10; i++ {
num := rand.Int() % clusterSize
fmt.Println("kill node", num+1)
etcds[num].Kill()
etcds[num].Release()
time.Sleep(time.Second)
argGroup[num] = increasePeerAddressPort(argGroup[num], clusterSize)
// restart
etcds[num], err = os.StartProcess(EtcdBinPath, argGroup[num], procAttr)
if err != nil {
panic(err)
}
time.Sleep(time.Second)
}
c := etcd.NewClient(nil)
c.SyncCluster()
result, err := c.Set("foo", "bar", 0)
if err != nil || result.Node.Key != "/foo" || result.Node.Value != "bar" {
t.Fatal("Failed to set value in etcd cluster")
}
}
// Create a five-node cluster
// Replace one of the nodes with different peer address
func TestReplaceWithDifferentPeerAddress(t *testing.T) {
// TODO(yichengq): find some way to avoid the error that will be
// caused if some node joins the cluster with the collided name.
// Possible solutions:
// 1. Remove itself when executing a join command with the same name
// and different peer address. However, it should find some way to
// trigger that execution because the leader may update its address
// and stop heartbeat.
// 2. Remove the node with the same name before join each time.
// But this way could be rather overkill.
t.Skip("Unimplemented functionality")
procAttr := new(os.ProcAttr)
procAttr.Files = []*os.File{nil, os.Stdout, os.Stderr}
clusterSize := 5
argGroup, etcds, err := CreateCluster(clusterSize, procAttr, false)
if err != nil {
t.Fatal("cannot create cluster")
}
defer DestroyCluster(etcds)
time.Sleep(2 * time.Second)
rand.Int()
for i := 0; i < 10; i++ {
num := rand.Int() % clusterSize
fmt.Println("replace node", num+1)
argGroup[num] = increasePeerAddressPort(argGroup[num], clusterSize)
argGroup[num] = increaseAddressPort(argGroup[num], clusterSize)
argGroup[num] = increaseDataDir(argGroup[num], clusterSize)
// restart
newEtcd, err := os.StartProcess(EtcdBinPath, append(argGroup[num], "-f"), procAttr)
if err != nil {
panic(err)
}
etcds[num].Wait()
etcds[num] = newEtcd
}
c := etcd.NewClient(nil)
c.SyncCluster()
result, err := c.Set("foo", "bar", 0)
if err != nil || result.Node.Key != "/foo" || result.Node.Value != "bar" {
t.Fatal("Failed to set value in etcd cluster")
}
}
// Create a five-node cluster
// Let the sixth instance join with different name and existing peer address
func TestRejoinWithDifferentName(t *testing.T) {
procAttr := new(os.ProcAttr)
procAttr.Files = []*os.File{nil, os.Stdout, os.Stderr}
clusterSize := 5
argGroup, etcds, err := CreateCluster(clusterSize, procAttr, false)
if err != nil {
t.Fatal("cannot create cluster")
}
defer DestroyCluster(etcds)
time.Sleep(2 * time.Second)
num := rand.Int() % clusterSize
fmt.Println("join node 6 that collides with node", num+1)
// kill
etcds[num].Kill()
etcds[num].Release()
time.Sleep(time.Second)
for i := 0; i < 2; i++ {
// restart
if i == 0 {
etcds[num], err = os.StartProcess(EtcdBinPath, append(argGroup[num], "-name=node6", "-peers=127.0.0.1:7002"), procAttr)
} else {
etcds[num], err = os.StartProcess(EtcdBinPath, append(argGroup[num], "-f", "-name=node6", "-peers=127.0.0.1:7002"), procAttr)
}
if err != nil {
t.Fatal("failed to start process:", err)
}
timer := time.AfterFunc(10*time.Second, func() {
t.Fatal("new etcd should fail immediately")
})
etcds[num].Wait()
etcds[num] = nil
timer.Stop()
}
}

View File

@ -1,200 +0,0 @@
// +build ignore
package test
import (
"bytes"
"fmt"
"math/rand"
"net/http"
"os"
"syscall"
"testing"
"time"
"github.com/coreos/etcd/third_party/github.com/coreos/go-etcd/etcd"
"github.com/coreos/etcd/tests"
"github.com/coreos/etcd/third_party/github.com/stretchr/testify/assert"
)
// remove the node and node rejoin with previous log
func TestRemoveNode(t *testing.T) {
procAttr := new(os.ProcAttr)
procAttr.Files = []*os.File{nil, os.Stdout, os.Stderr}
clusterSize := 4
argGroup, etcds, _ := CreateCluster(clusterSize, procAttr, false)
defer DestroyCluster(etcds)
time.Sleep(time.Second)
c := etcd.NewClient(nil)
c.SyncCluster()
resp, _ := tests.Put("http://localhost:7001/v2/admin/config", "application/json", bytes.NewBufferString(`{"activeSize":4, "syncInterval":5}`))
if !assert.Equal(t, resp.StatusCode, 200) {
t.FailNow()
}
rmReq, _ := http.NewRequest("DELETE", "http://127.0.0.1:7001/remove/node3", nil)
client := &http.Client{}
for i := 0; i < 2; i++ {
for i := 0; i < 2; i++ {
client.Do(rmReq)
fmt.Println("send remove to node3 and wait for its exiting")
time.Sleep(100 * time.Millisecond)
resp, err := c.Get("_etcd/machines", false, false)
if err != nil {
panic(err)
}
if len(resp.Node.Nodes) != 3 {
t.Fatal("cannot remove peer")
}
etcds[2].Kill()
etcds[2].Wait()
if i == 1 {
// rejoin with log
etcds[2], err = os.StartProcess(EtcdBinPath, argGroup[2], procAttr)
} else {
// rejoin without log
etcds[2], err = os.StartProcess(EtcdBinPath, append(argGroup[2], "-f"), procAttr)
}
if err != nil {
panic(err)
}
time.Sleep(time.Second + 5*time.Second)
resp, err = c.Get("_etcd/machines", false, false)
if err != nil {
panic(err)
}
if len(resp.Node.Nodes) != 4 {
t.Fatalf("add peer fails #1 (%d != 4)", len(resp.Node.Nodes))
}
}
// first kill the node, then remove it, then add it back
for i := 0; i < 2; i++ {
etcds[2].Kill()
fmt.Println("kill node3 and wait for its exiting")
etcds[2].Wait()
client.Do(rmReq)
time.Sleep(100 * time.Millisecond)
resp, err := c.Get("_etcd/machines", false, false)
if err != nil {
panic(err)
}
if len(resp.Node.Nodes) != 3 {
t.Fatal("cannot remove peer")
}
if i == 1 {
// rejoin with log
etcds[2], err = os.StartProcess(EtcdBinPath, append(argGroup[2]), procAttr)
} else {
// rejoin without log
etcds[2], err = os.StartProcess(EtcdBinPath, append(argGroup[2], "-f"), procAttr)
}
if err != nil {
panic(err)
}
time.Sleep(time.Second + time.Second)
resp, err = c.Get("_etcd/machines", false, false)
if err != nil {
panic(err)
}
if len(resp.Node.Nodes) != 4 {
t.Fatalf("add peer fails #2 (%d != 4)", len(resp.Node.Nodes))
}
}
}
}
func TestRemovePausedNode(t *testing.T) {
procAttr := new(os.ProcAttr)
procAttr.Files = []*os.File{nil, os.Stdout, os.Stderr}
clusterSize := 4
_, etcds, _ := CreateCluster(clusterSize, procAttr, false)
defer DestroyCluster(etcds)
time.Sleep(time.Second)
c := etcd.NewClient(nil)
c.SyncCluster()
r, _ := tests.Put("http://localhost:7001/v2/admin/config", "application/json", bytes.NewBufferString(`{"activeSize":3, "removeDelay":1, "syncInterval":1}`))
if !assert.Equal(t, r.StatusCode, 200) {
t.FailNow()
}
// Wait for standby instances to update its cluster config
time.Sleep(6 * time.Second)
resp, err := c.Get("_etcd/machines", false, false)
if err != nil {
panic(err)
}
if len(resp.Node.Nodes) != 3 {
t.Fatal("cannot remove peer")
}
for i := 0; i < clusterSize; i++ {
// first pause the node, then remove it, then resume it
idx := rand.Int() % clusterSize
etcds[idx].Signal(syscall.SIGSTOP)
fmt.Printf("pause node%d and let standby node take its place\n", idx+1)
time.Sleep(4 * time.Second)
etcds[idx].Signal(syscall.SIGCONT)
// let it change its state to candidate at least
time.Sleep(time.Second)
stop := make(chan bool)
leaderChan := make(chan string, 1)
all := make(chan bool, 1)
go Monitor(clusterSize, clusterSize, leaderChan, all, stop)
<-all
<-leaderChan
stop <- true
resp, err = c.Get("_etcd/machines", false, false)
if err != nil {
panic(err)
}
if len(resp.Node.Nodes) != 3 {
t.Fatalf("add peer fails (%d != 3)", len(resp.Node.Nodes))
}
for i := 0; i < 3; i++ {
if resp.Node.Nodes[i].Key == fmt.Sprintf("node%d", idx+1) {
t.Fatal("node should be removed")
}
}
}
}

View File

@ -1,67 +0,0 @@
// +build ignore
package test
import (
"os"
"testing"
"time"
"github.com/coreos/etcd/third_party/github.com/coreos/go-etcd/etcd"
)
func TestSimpleMultiNode(t *testing.T) {
templateTestSimpleMultiNode(t, false)
}
func TestSimpleMultiNodeTls(t *testing.T) {
templateTestSimpleMultiNode(t, true)
}
// Create a three nodes and try to set value
func templateTestSimpleMultiNode(t *testing.T, tls bool) {
procAttr := new(os.ProcAttr)
procAttr.Files = []*os.File{nil, os.Stdout, os.Stderr}
clusterSize := 3
_, etcds, err := CreateCluster(clusterSize, procAttr, tls)
if err != nil {
t.Fatalf("cannot create cluster: %v", err)
}
defer DestroyCluster(etcds)
time.Sleep(time.Second)
c := etcd.NewClient(nil)
if c.SyncCluster() == false {
t.Fatal("Cannot sync cluster!")
}
// Test Set
result, err := c.Set("foo", "bar", 100)
if err != nil {
t.Fatal(err)
}
node := result.Node
if node.Key != "/foo" || node.Value != "bar" || node.TTL < 95 {
t.Fatalf("Set 1 failed with %s %s %v", node.Key, node.Value, node.TTL)
}
time.Sleep(time.Second)
result, err = c.Set("foo", "bar", 100)
if err != nil {
t.Fatal(err)
}
node = result.Node
if node.Key != "/foo" || node.Value != "bar" || node.TTL < 95 {
t.Fatalf("Set 2 failed with %s %s %v", node.Key, node.Value, node.TTL)
}
}

View File

@ -1,151 +0,0 @@
// +build ignore
package test
import (
"io/ioutil"
"os"
"strconv"
"testing"
"time"
"github.com/coreos/etcd/third_party/github.com/coreos/go-etcd/etcd"
)
// This test creates a single node and then set a value to it to trigger snapshot
func TestSnapshot(t *testing.T) {
procAttr := new(os.ProcAttr)
procAttr.Files = []*os.File{nil, os.Stdout, os.Stderr}
args := []string{"etcd", "-name=node1", "-data-dir=/tmp/node1", "-snapshot=true", "-snapshot-count=500"}
process, err := os.StartProcess(EtcdBinPath, append(args, "-f"), procAttr)
if err != nil {
t.Fatal("start process failed:" + err.Error())
}
defer process.Kill()
time.Sleep(time.Second)
c := etcd.NewClient(nil)
c.SyncCluster()
// issue first 501 commands
for i := 0; i < 501; i++ {
result, err := c.Set("foo", "bar", 100)
node := result.Node
if err != nil || node.Key != "/foo" || node.Value != "bar" || node.TTL < 95 {
if err != nil {
t.Fatal(err)
}
t.Fatalf("Set failed with %s %s %v", node.Key, node.Value, node.TTL)
}
}
// wait for a snapshot interval
time.Sleep(3 * time.Second)
snapshots, err := ioutil.ReadDir("/tmp/node1/snapshot")
if err != nil {
t.Fatal("list snapshot failed:" + err.Error())
}
if len(snapshots) != 1 {
t.Fatal("wrong number of snapshot :[1/", len(snapshots), "]")
}
index, _ := strconv.Atoi(snapshots[0].Name()[2:5])
if index < 503 || index > 516 {
t.Fatal("wrong name of snapshot :", snapshots[0].Name())
}
// issue second 501 commands
for i := 0; i < 501; i++ {
result, err := c.Set("foo", "bar", 100)
node := result.Node
if err != nil || node.Key != "/foo" || node.Value != "bar" || node.TTL < 95 {
if err != nil {
t.Fatal(err)
}
t.Fatalf("Set failed with %s %s %v", node.Key, node.Value, node.TTL)
}
}
// wait for a snapshot interval
time.Sleep(3 * time.Second)
snapshots, err = ioutil.ReadDir("/tmp/node1/snapshot")
if err != nil {
t.Fatal("list snapshot failed:" + err.Error())
}
if len(snapshots) != 1 {
t.Fatal("wrong number of snapshot :[1/", len(snapshots), "]")
}
index, _ = strconv.Atoi(snapshots[0].Name()[2:6])
if index < 1010 || index > 1029 {
t.Fatal("wrong name of snapshot :", snapshots[0].Name())
}
}
// TestSnapshotRestart tests etcd restarts with snapshot file
func TestSnapshotRestart(t *testing.T) {
procAttr := new(os.ProcAttr)
procAttr.Files = []*os.File{nil, os.Stdout, os.Stderr}
args := []string{"etcd", "-name=node1", "-data-dir=/tmp/node1", "-snapshot=true", "-snapshot-count=500"}
process, err := os.StartProcess(EtcdBinPath, append(args, "-f"), procAttr)
if err != nil {
t.Fatal("start process failed:" + err.Error())
}
time.Sleep(time.Second)
c := etcd.NewClient(nil)
c.SyncCluster()
// issue first 501 commands
for i := 0; i < 501; i++ {
result, err := c.Set("foo", "bar", 100)
node := result.Node
if err != nil || node.Key != "/foo" || node.Value != "bar" || node.TTL < 95 {
if err != nil {
t.Fatal(err)
}
t.Fatalf("Set failed with %s %s %v", node.Key, node.Value, node.TTL)
}
}
// wait for a snapshot interval
time.Sleep(3 * time.Second)
_, err = ioutil.ReadDir("/tmp/node1/snapshot")
if err != nil {
t.Fatal("list snapshot failed:" + err.Error())
}
process.Kill()
process, err = os.StartProcess(EtcdBinPath, args, procAttr)
if err != nil {
t.Fatal("start process failed:" + err.Error())
}
defer process.Kill()
time.Sleep(1 * time.Second)
_, err = c.Set("foo", "bar", 100)
if err != nil {
t.Fatal(err)
}
}

View File

@ -1,342 +0,0 @@
// +build ignore
package test
import (
"bytes"
"fmt"
"os"
"testing"
"time"
"github.com/coreos/etcd/server"
"github.com/coreos/etcd/store"
"github.com/coreos/etcd/tests"
"github.com/coreos/etcd/third_party/github.com/coreos/go-etcd/etcd"
"github.com/coreos/etcd/third_party/github.com/stretchr/testify/assert"
)
// Create a full cluster and then change the active size.
func TestStandby(t *testing.T) {
clusterSize := 15
_, etcds, err := CreateCluster(clusterSize, &os.ProcAttr{Files: []*os.File{nil, os.Stdout, os.Stderr}}, false)
if !assert.NoError(t, err) {
t.Fatal("cannot create cluster")
}
defer DestroyCluster(etcds)
resp, _ := tests.Put("http://localhost:7001/v2/admin/config", "application/json", bytes.NewBufferString(`{"syncInterval":1}`))
if !assert.Equal(t, resp.StatusCode, 200) {
t.FailNow()
}
time.Sleep(time.Second)
c := etcd.NewClient(nil)
c.SyncCluster()
// Verify that we just have default machines.
result, err := c.Get("_etcd/machines", false, true)
assert.NoError(t, err)
assert.Equal(t, len(result.Node.Nodes), 9)
t.Log("Reconfigure with a smaller active size")
resp, _ = tests.Put("http://localhost:7001/v2/admin/config", "application/json", bytes.NewBufferString(`{"activeSize":7, "syncInterval":1}`))
if !assert.Equal(t, resp.StatusCode, 200) {
t.FailNow()
}
// Wait for two monitor cycles before checking for demotion.
time.Sleep((2 * server.ActiveMonitorTimeout) + (2 * time.Second))
// Verify that we now have seven peers.
result, err = c.Get("_etcd/machines", false, true)
assert.NoError(t, err)
assert.Equal(t, len(result.Node.Nodes), 7)
t.Log("Test the functionality of all servers")
// Set key.
time.Sleep(time.Second)
if _, err := c.Set("foo", "bar", 0); err != nil {
panic(err)
}
time.Sleep(time.Second)
// Check that all peers and standbys have the value.
for i := range etcds {
resp, err := tests.Get(fmt.Sprintf("http://localhost:%d/v2/keys/foo", 4000+(i+1)))
if assert.NoError(t, err) {
body := tests.ReadBodyJSON(resp)
if node, _ := body["node"].(map[string]interface{}); assert.NotNil(t, node) {
assert.Equal(t, node["value"], "bar")
}
}
}
t.Log("Reconfigure with larger active size and wait for join")
resp, _ = tests.Put("http://localhost:7001/v2/admin/config", "application/json", bytes.NewBufferString(`{"activeSize":8, "syncInterval":1}`))
if !assert.Equal(t, resp.StatusCode, 200) {
t.FailNow()
}
time.Sleep((1 * time.Second) + (1 * time.Second))
// Verify that exactly eight machines are in the cluster.
result, err = c.Get("_etcd/machines", false, true)
assert.NoError(t, err)
assert.Equal(t, len(result.Node.Nodes), 8)
}
// Create a full cluster, disconnect a peer, wait for removal, wait for standby join.
func TestStandbyAutoJoin(t *testing.T) {
clusterSize := 5
_, etcds, err := CreateCluster(clusterSize, &os.ProcAttr{Files: []*os.File{nil, os.Stdout, os.Stderr}}, false)
if err != nil {
t.Fatal("cannot create cluster")
}
defer func() {
// Wrap this in a closure so that it picks up the updated version of
// the "etcds" variable.
DestroyCluster(etcds)
}()
c := etcd.NewClient(nil)
c.SyncCluster()
time.Sleep(1 * time.Second)
// Verify that we have five machines.
result, err := c.Get("_etcd/machines", false, true)
assert.NoError(t, err)
assert.Equal(t, len(result.Node.Nodes), 5)
// Reconfigure with a short remove delay (2 second).
resp, _ := tests.Put("http://localhost:7001/v2/admin/config", "application/json", bytes.NewBufferString(`{"activeSize":4, "removeDelay":2, "syncInterval":1}`))
if !assert.Equal(t, resp.StatusCode, 200) {
t.FailNow()
}
// Wait for a monitor cycle before checking for removal.
time.Sleep(server.ActiveMonitorTimeout + (1 * time.Second))
// Verify that we now have four peers.
result, err = c.Get("_etcd/machines", false, true)
assert.NoError(t, err)
assert.Equal(t, len(result.Node.Nodes), 4)
// Remove peer.
etcd := etcds[1]
etcds = append(etcds[:1], etcds[2:]...)
if err := etcd.Kill(); err != nil {
panic(err.Error())
}
etcd.Release()
// Wait for it to get dropped.
time.Sleep(server.PeerActivityMonitorTimeout + (1 * time.Second))
// Wait for the standby to join.
time.Sleep((1 * time.Second) + (1 * time.Second))
// Verify that we have 4 peers.
result, err = c.Get("_etcd/machines", true, true)
assert.NoError(t, err)
assert.Equal(t, len(result.Node.Nodes), 4)
// Verify that node2 is not one of those peers.
_, err = c.Get("_etcd/machines/node2", false, false)
assert.Error(t, err)
}
// Create a full cluster and then change the active size gradually.
func TestStandbyGradualChange(t *testing.T) {
clusterSize := 9
_, etcds, err := CreateCluster(clusterSize, &os.ProcAttr{Files: []*os.File{nil, os.Stdout, os.Stderr}}, false)
assert.NoError(t, err)
defer DestroyCluster(etcds)
if err != nil {
t.Fatal("cannot create cluster")
}
time.Sleep(time.Second)
c := etcd.NewClient(nil)
c.SyncCluster()
num := clusterSize
for inc := 0; inc < 2; inc++ {
for i := 0; i < 6; i++ {
// Verify that we just have i machines.
result, err := c.Get("_etcd/machines", false, true)
assert.NoError(t, err)
assert.Equal(t, len(result.Node.Nodes), num)
if inc == 0 {
num--
} else {
num++
}
t.Log("Reconfigure with active size", num)
resp, _ := tests.Put("http://localhost:7001/v2/admin/config", "application/json", bytes.NewBufferString(fmt.Sprintf(`{"activeSize":%d, "syncInterval":1}`, num)))
if !assert.Equal(t, resp.StatusCode, 200) {
t.FailNow()
}
if inc == 0 {
// Wait for monitor cycles before checking for demotion.
time.Sleep(server.ActiveMonitorTimeout + (1 * time.Second))
} else {
time.Sleep(time.Second + (1 * time.Second))
}
// Verify that we now have peers.
result, err = c.Get("_etcd/machines", false, true)
assert.NoError(t, err)
assert.Equal(t, len(result.Node.Nodes), num)
t.Log("Test the functionality of all servers")
// Set key.
if _, err := c.Set("foo", "bar", 0); err != nil {
panic(err)
}
time.Sleep(100 * time.Millisecond)
// Check that all peers and standbys have the value.
for i := range etcds {
resp, err := tests.Get(fmt.Sprintf("http://localhost:%d/v2/keys/foo", 4000+(i+1)))
if assert.NoError(t, err) {
body := tests.ReadBodyJSON(resp)
if node, _ := body["node"].(map[string]interface{}); assert.NotNil(t, node) {
assert.Equal(t, node["value"], "bar")
}
}
}
}
}
}
// Create a full cluster and then change the active size dramatically.
func TestStandbyDramaticChange(t *testing.T) {
clusterSize := 9
_, etcds, err := CreateCluster(clusterSize, &os.ProcAttr{Files: []*os.File{nil, os.Stdout, os.Stderr}}, false)
assert.NoError(t, err)
defer DestroyCluster(etcds)
if err != nil {
t.Fatal("cannot create cluster")
}
time.Sleep(time.Second)
c := etcd.NewClient(nil)
c.SyncCluster()
num := clusterSize
for i := 0; i < 3; i++ {
for inc := 0; inc < 2; inc++ {
// Verify that we just have i machines.
result, err := c.Get("_etcd/machines", false, true)
assert.NoError(t, err)
assert.Equal(t, len(result.Node.Nodes), num)
if inc == 0 {
num -= 6
} else {
num += 6
}
t.Log("Reconfigure with active size", num)
resp, _ := tests.Put("http://localhost:7001/v2/admin/config", "application/json", bytes.NewBufferString(fmt.Sprintf(`{"activeSize":%d, "syncInterval":1}`, num)))
if !assert.Equal(t, resp.StatusCode, 200) {
t.FailNow()
}
if inc == 0 {
// Wait for monitor cycles before checking for demotion.
time.Sleep(6*server.ActiveMonitorTimeout + (1 * time.Second))
} else {
time.Sleep(time.Second + (1 * time.Second))
}
// Verify that we now have peers.
result, err = c.Get("_etcd/machines", false, true)
assert.NoError(t, err)
assert.Equal(t, len(result.Node.Nodes), num)
t.Log("Test the functionality of all servers")
// Set key.
if _, err := c.Set("foo", "bar", 0); err != nil {
panic(err)
}
time.Sleep(100 * time.Millisecond)
// Check that all peers and standbys have the value.
for i := range etcds {
resp, err := tests.Get(fmt.Sprintf("http://localhost:%d/v2/keys/foo", 4000+(i+1)))
if assert.NoError(t, err) {
body := tests.ReadBodyJSON(resp)
if node, _ := body["node"].(map[string]interface{}); assert.NotNil(t, node) {
assert.Equal(t, node["value"], "bar")
}
}
}
}
}
}
func TestStandbyJoinMiss(t *testing.T) {
clusterSize := 2
_, etcds, err := CreateCluster(clusterSize, &os.ProcAttr{Files: []*os.File{nil, os.Stdout, os.Stderr}}, false)
if err != nil {
t.Fatal("cannot create cluster")
}
defer DestroyCluster(etcds)
c := etcd.NewClient(nil)
c.SyncCluster()
time.Sleep(1 * time.Second)
// Verify that we have two machines.
result, err := c.Get("_etcd/machines", false, true)
assert.NoError(t, err)
assert.Equal(t, len(result.Node.Nodes), clusterSize)
resp, _ := tests.Put("http://localhost:7001/v2/admin/config", "application/json", bytes.NewBufferString(`{"removeDelay":4, "syncInterval":4}`))
if !assert.Equal(t, resp.StatusCode, 200) {
t.FailNow()
}
time.Sleep(time.Second)
resp, _ = tests.Delete("http://localhost:7001/v2/admin/machines/node2", "application/json", nil)
if !assert.Equal(t, resp.StatusCode, 200) {
t.FailNow()
}
// Wait for a monitor cycle before checking for removal.
time.Sleep(server.ActiveMonitorTimeout + (1 * time.Second))
// Verify that we now have one peer.
result, err = c.Get("_etcd/machines", false, true)
assert.NoError(t, err)
assert.Equal(t, len(result.Node.Nodes), 1)
// Simulate the join failure
_, err = server.NewClient(nil).AddMachine("http://localhost:7001",
&server.JoinCommand{
MinVersion: store.MinVersion(),
MaxVersion: store.MaxVersion(),
Name: "node2",
RaftURL: "http://127.0.0.1:7002",
EtcdURL: "http://127.0.0.1:4002",
})
assert.NoError(t, err)
time.Sleep(6 * time.Second)
go tests.Delete("http://localhost:7001/v2/admin/machines/node2", "application/json", nil)
time.Sleep(time.Second)
result, err = c.Get("_etcd/machines", false, true)
assert.NoError(t, err)
assert.Equal(t, len(result.Node.Nodes), 1)
}

View File

@ -1,258 +0,0 @@
// +build ignore
/*
Copyright 2013 CoreOS Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package test
import (
"errors"
"fmt"
"io/ioutil"
"net"
"net/http"
"os"
"strconv"
"time"
"github.com/coreos/etcd/third_party/github.com/coreos/go-etcd/etcd"
)
var client = http.Client{
Transport: &http.Transport{
Dial: dialTimeoutFast,
},
}
// Sending set commands
func Set(stop chan bool) {
stopSet := false
i := 0
c := etcd.NewClient(nil)
for {
key := fmt.Sprintf("%s_%v", "foo", i)
result, err := c.Set(key, "bar", 0)
if err != nil || result.Node.Key != "/"+key || result.Node.Value != "bar" {
select {
case <-stop:
stopSet = true
default:
}
}
select {
case <-stop:
stopSet = true
default:
}
if stopSet {
break
}
i++
}
stop <- true
}
func WaitForServer(host string, client http.Client, scheme string) error {
path := fmt.Sprintf("%s://%s/v2/keys/", scheme, host)
var resp *http.Response
var err error
for i := 0; i < 10; i++ {
time.Sleep(1 * time.Second)
resp, err = client.Get(path)
if err == nil && resp.StatusCode == 200 {
return nil
}
}
return errors.New(fmt.Sprintf("etcd server was not reachable in a long time, last-time response and error: %v; %v", resp, err))
}
// Create a cluster of etcd nodes
func CreateCluster(size int, procAttr *os.ProcAttr, ssl bool) ([][]string, []*os.Process, error) {
argGroup := make([][]string, size)
sslServer1 := []string{"-peer-ca-file=../../fixtures/ca/ca.crt",
"-peer-cert-file=../../fixtures/ca/server.crt",
"-peer-key-file=../../fixtures/ca/server.key.insecure",
}
sslServer2 := []string{"-peer-ca-file=../../fixtures/ca/ca.crt",
"-peer-cert-file=../../fixtures/ca/server2.crt",
"-peer-key-file=../../fixtures/ca/server2.key.insecure",
}
for i := 0; i < size; i++ {
if i == 0 {
argGroup[i] = []string{"etcd", "-data-dir=/tmp/node1", "-name=node1", "-cluster-remove-delay=1800"}
if ssl {
argGroup[i] = append(argGroup[i], sslServer1...)
}
} else {
strI := strconv.Itoa(i + 1)
argGroup[i] = []string{"etcd", "-name=node" + strI, fmt.Sprintf("-addr=127.0.0.1:%d", 4001+i), fmt.Sprintf("-peer-addr=127.0.0.1:%d", 7001+i), "-data-dir=/tmp/node" + strI, "-peers=127.0.0.1:7001", "-cluster-remove-delay=1800"}
if ssl {
argGroup[i] = append(argGroup[i], sslServer2...)
}
}
}
etcds := make([]*os.Process, size)
for i := range etcds {
var err error
etcds[i], err = os.StartProcess(EtcdBinPath, append(argGroup[i], "-f"), procAttr)
if err != nil {
return nil, nil, err
}
// The problem is that if the master isn't up then the children
// have to retry. This retry can take upwards of 15 seconds
// which slows tests way down and some of them fail.
//
// Waiting for each server to start when ssl is a workaround.
// Autotest machines are dramatically slow, and it could spend
// several seconds to build TSL connections between servers. That
// is extremely terribe when the second machine joins the cluster
// because the cluster is out of work at this time. The guy
// tries to join during this time will fail, and current implementation
// makes it fail after just one-time try(bug in #661). This
// makes the cluster start with N-1 machines.
// TODO(yichengq): It should be fixed.
if i == 0 || ssl {
client := buildClient()
err = WaitForServer("127.0.0.1:400"+strconv.Itoa(i+1), client, "http")
if err != nil {
return nil, nil, err
}
}
}
return argGroup, etcds, nil
}
// Destroy all the nodes in the cluster
func DestroyCluster(etcds []*os.Process) error {
for _, etcd := range etcds {
if etcd == nil {
continue
}
err := etcd.Kill()
if err != nil {
panic(err.Error())
}
etcd.Release()
}
return nil
}
//
func Monitor(size int, allowDeadNum int, leaderChan chan string, all chan bool, stop chan bool) {
leaderMap := make(map[int]string)
baseAddrFormat := "http://0.0.0.0:%d"
for {
knownLeader := "unknown"
dead := 0
var i int
for i = 0; i < size; i++ {
leader, err := getLeader(fmt.Sprintf(baseAddrFormat, i+4001))
if err == nil {
leaderMap[i] = leader
if knownLeader == "unknown" {
knownLeader = leader
} else {
if leader != knownLeader {
break
}
}
} else {
dead++
if dead > allowDeadNum {
break
}
}
}
if i == size {
select {
case <-stop:
return
case <-leaderChan:
leaderChan <- knownLeader
default:
leaderChan <- knownLeader
}
}
if dead == 0 {
select {
case <-all:
all <- true
default:
all <- true
}
}
time.Sleep(time.Millisecond * 10)
}
}
func getLeader(addr string) (string, error) {
resp, err := client.Get(addr + "/v2/leader")
if err != nil {
return "", err
}
if resp.StatusCode != http.StatusOK {
resp.Body.Close()
return "", fmt.Errorf("no leader")
}
b, err := ioutil.ReadAll(resp.Body)
resp.Body.Close()
if err != nil {
return "", err
}
return string(b), nil
}
// Dial with timeout
func dialTimeoutFast(network, addr string) (net.Conn, error) {
return net.DialTimeout(network, addr, time.Millisecond*10)
}

View File

@ -1,80 +0,0 @@
// +build ignore
package tests
import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"strings"
)
// Creates a new HTTP client with KeepAlive disabled.
func NewHTTPClient() *http.Client {
return &http.Client{Transport: &http.Transport{DisableKeepAlives: true}}
}
// Reads the body from the response and closes it.
func ReadBody(resp *http.Response) []byte {
if resp == nil {
return []byte{}
}
body, _ := ioutil.ReadAll(resp.Body)
resp.Body.Close()
return body
}
// Reads the body from the response and parses it as JSON.
func ReadBodyJSON(resp *http.Response) map[string]interface{} {
m := make(map[string]interface{})
b := ReadBody(resp)
if err := json.Unmarshal(b, &m); err != nil {
panic(fmt.Sprintf("HTTP body JSON parse error: %v: %s", err, string(b)))
}
return m
}
func Head(url string) (*http.Response, error) {
return send("HEAD", url, "application/json", nil)
}
func Get(url string) (*http.Response, error) {
return send("GET", url, "application/json", nil)
}
func Post(url string, bodyType string, body io.Reader) (*http.Response, error) {
return send("POST", url, bodyType, body)
}
func PostForm(url string, data url.Values) (*http.Response, error) {
return Post(url, "application/x-www-form-urlencoded", strings.NewReader(data.Encode()))
}
func Put(url string, bodyType string, body io.Reader) (*http.Response, error) {
return send("PUT", url, bodyType, body)
}
func PutForm(url string, data url.Values) (*http.Response, error) {
return Put(url, "application/x-www-form-urlencoded", strings.NewReader(data.Encode()))
}
func Delete(url string, bodyType string, body io.Reader) (*http.Response, error) {
return send("DELETE", url, bodyType, body)
}
func DeleteForm(url string, data url.Values) (*http.Response, error) {
return Delete(url, "application/x-www-form-urlencoded", strings.NewReader(data.Encode()))
}
func send(method string, url string, bodyType string, body io.Reader) (*http.Response, error) {
c := NewHTTPClient()
req, err := http.NewRequest(method, url, body)
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", bodyType)
return c.Do(req)
}

View File

@ -1,69 +0,0 @@
// +build ignore
package mock
import (
"time"
"github.com/coreos/etcd/store"
"github.com/stretchr/testify/mock"
)
// A mock Store object used for testing.
type Store struct {
mock.Mock
}
func NewStore() *Store {
return &Store{}
}
func (s *Store) Get(nodePath string, recursive, sorted bool, index uint64, term uint64) (*store.Event, error) {
args := s.Called(nodePath, recursive, sorted, index, term)
return args.Get(0).(*store.Event), args.Error(1)
}
func (s *Store) Set(nodePath string, value string, expireTime time.Time, index uint64, term uint64) (*store.Event, error) {
args := s.Called(nodePath, value, expireTime, index, term)
return args.Get(0).(*store.Event), args.Error(1)
}
func (s *Store) Update(nodePath string, newValue string, expireTime time.Time, index uint64, term uint64) (*store.Event, error) {
args := s.Called(nodePath, newValue, expireTime, index, term)
return args.Get(0).(*store.Event), args.Error(1)
}
func (s *Store) Create(nodePath string, value string, incrementalSuffix bool, expireTime time.Time, index uint64, term uint64) (*store.Event, error) {
args := s.Called(nodePath, value, incrementalSuffix, expireTime, index, term)
return args.Get(0).(*store.Event), args.Error(1)
}
func (s *Store) CompareAndSwap(nodePath string, prevValue string, prevIndex uint64, value string, expireTime time.Time, index uint64, term uint64) (*store.Event, error) {
args := s.Called(nodePath, prevValue, prevIndex, value, expireTime, index, term)
return args.Get(0).(*store.Event), args.Error(1)
}
func (s *Store) Delete(nodePath string, recursive bool, index uint64, term uint64) (*store.Event, error) {
args := s.Called(nodePath, recursive, index, term)
return args.Get(0).(*store.Event), args.Error(1)
}
func (s *Store) Watch(prefix string, recursive bool, sinceIndex uint64, index uint64, term uint64) (<-chan *store.Event, error) {
args := s.Called(prefix, recursive, sinceIndex, index, term)
return args.Get(0).(<-chan *store.Event), args.Error(1)
}
func (s *Store) Save() ([]byte, error) {
args := s.Called()
return args.Get(0).([]byte), args.Error(1)
}
func (s *Store) Recovery(b []byte) error {
args := s.Called(b)
return args.Error(1)
}
func (s *Store) JsonStats() []byte {
args := s.Called()
return args.Get(0).([]byte)
}

View File

@ -1,62 +0,0 @@
// +build ignore
package mock
import (
"net/http"
"github.com/coreos/etcd/store"
"github.com/coreos/etcd/third_party/github.com/goraft/raft"
"github.com/stretchr/testify/mock"
)
// A mock Server for the v2 handlers.
type ServerV2 struct {
mock.Mock
store store.Store
}
func NewServerV2(store store.Store) *ServerV2 {
return &ServerV2{
store: store,
}
}
func (s *ServerV2) State() string {
args := s.Called()
return args.String(0)
}
func (s *ServerV2) Leader() string {
args := s.Called()
return args.String(0)
}
func (s *ServerV2) CommitIndex() uint64 {
args := s.Called()
return args.Get(0).(uint64)
}
func (s *ServerV2) Term() uint64 {
args := s.Called()
return args.Get(0).(uint64)
}
func (s *ServerV2) PeerURL(name string) (string, bool) {
args := s.Called(name)
return args.String(0), args.Bool(1)
}
func (s *ServerV2) ClientURL(name string) (string, bool) {
args := s.Called(name)
return args.String(0), args.Bool(1)
}
func (s *ServerV2) Store() store.Store {
return s.store
}
func (s *ServerV2) Dispatch(c raft.Command, w http.ResponseWriter, req *http.Request) error {
args := s.Called(c, w, req)
return args.Error(0)
}

View File

@ -1,42 +0,0 @@
// +build ignore
package tests
import (
"github.com/coreos/etcd/config"
"github.com/coreos/etcd/etcd"
"github.com/coreos/etcd/server"
)
const (
testName = "ETCDTEST"
testClientURL = "localhost:4401"
testRaftURL = "localhost:7701"
testSnapshotCount = 10000
testHeartbeatInterval = 50
testElectionTimeout = 200
testDataDir = "/tmp/ETCDTEST"
)
// Starts a new server.
func RunServer(f func(*server.Server)) {
c := cfg.New()
c.Name = testName
c.Addr = testClientURL
c.Peer.Addr = testRaftURL
c.DataDir = testDataDir
c.Force = true
c.Peer.HeartbeatInterval = testHeartbeatInterval
c.Peer.ElectionTimeout = testElectionTimeout
c.SnapshotCount = testSnapshotCount
i := etcd.New(c)
go i.Run()
<-i.ReadyNotify()
// Execute the function passed in.
f(i.Server)
i.Stop()
}