diff --git a/conf/cluster_config.go b/conf/cluster_config.go deleted file mode 100644 index 1ffd10abf..000000000 --- a/conf/cluster_config.go +++ /dev/null @@ -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 - } -} diff --git a/conf/config.go b/conf/config.go deleted file mode 100644 index cbe055736..000000000 --- a/conf/config.go +++ /dev/null @@ -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 -} diff --git a/conf/config_test.go b/conf/config_test.go deleted file mode 100644 index 72b3cb062..000000000 --- a/conf/config_test.go +++ /dev/null @@ -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) -} diff --git a/conf/default.go b/conf/default.go deleted file mode 100644 index 214351e7b..000000000 --- a/conf/default.go +++ /dev/null @@ -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) -) diff --git a/conf/tls_config.go b/conf/tls_config.go deleted file mode 100644 index 38573d5b7..000000000 --- a/conf/tls_config.go +++ /dev/null @@ -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) - } -} diff --git a/etcdserver/discovery.go b/etcdserver/discovery.go deleted file mode 100644 index deb633bed..000000000 --- a/etcdserver/discovery.go +++ /dev/null @@ -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 - } - } -} diff --git a/etcdserver/etcd.go b/etcdserver/etcd.go deleted file mode 100644 index 27da283f7..000000000 --- a/etcdserver/etcd.go +++ /dev/null @@ -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 - } - } -} diff --git a/etcdserver/etcd_functional_test.go b/etcdserver/etcd_functional_test.go deleted file mode 100644 index 898ef2706..000000000 --- a/etcdserver/etcd_functional_test.go +++ /dev/null @@ -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") -} diff --git a/etcdserver/etcd_start_test.go b/etcdserver/etcd_start_test.go deleted file mode 100644 index 1190a9f31..000000000 --- a/etcdserver/etcd_start_test.go +++ /dev/null @@ -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") -} diff --git a/etcdserver/etcd_test.go b/etcdserver/etcd_test.go deleted file mode 100644 index 3c88f33ba..000000000 --- a/etcdserver/etcd_test.go +++ /dev/null @@ -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 -} diff --git a/etcdserver2/etcdhttp/http.go b/etcdserver/etcdhttp/http.go similarity index 98% rename from etcdserver2/etcdhttp/http.go rename to etcdserver/etcdhttp/http.go index ab3db064b..752703158 100644 --- a/etcdserver2/etcdhttp/http.go +++ b/etcdserver/etcdhttp/http.go @@ -17,11 +17,12 @@ import ( crand "crypto/rand" "math/rand" + "code.google.com/p/go.net/context" "github.com/coreos/etcd/elog" etcderrors "github.com/coreos/etcd/error" - etcdserver "github.com/coreos/etcd/etcdserver2" - "github.com/coreos/etcd/etcdserver2/etcdserverpb" + "github.com/coreos/etcd/etcdserver" + "github.com/coreos/etcd/etcdserver/etcdserverpb" "github.com/coreos/etcd/raft/raftpb" "github.com/coreos/etcd/store" ) diff --git a/etcdserver2/etcdhttp/http_test.go b/etcdserver/etcdhttp/http_test.go similarity index 96% rename from etcdserver2/etcdhttp/http_test.go rename to etcdserver/etcdhttp/http_test.go index 92260e57f..11d5f9d53 100644 --- a/etcdserver2/etcdhttp/http_test.go +++ b/etcdserver/etcdhttp/http_test.go @@ -8,9 +8,10 @@ import ( "reflect" "testing" "time" + "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/raftpb" "github.com/coreos/etcd/store" diff --git a/etcdserver2/etcdserverpb/etcdserver.pb.go b/etcdserver/etcdserverpb/etcdserver.pb.go similarity index 100% rename from etcdserver2/etcdserverpb/etcdserver.pb.go rename to etcdserver/etcdserverpb/etcdserver.pb.go diff --git a/etcdserver2/etcdserverpb/etcdserver.proto b/etcdserver/etcdserverpb/etcdserver.proto similarity index 100% rename from etcdserver2/etcdserverpb/etcdserver.proto rename to etcdserver/etcdserverpb/etcdserver.proto diff --git a/etcdserver2/etcdserverpb/genproto.sh b/etcdserver/etcdserverpb/genproto.sh similarity index 100% rename from etcdserver2/etcdserverpb/genproto.sh rename to etcdserver/etcdserverpb/genproto.sh diff --git a/etcdserver2/example_test.go b/etcdserver/example_test.go similarity index 100% rename from etcdserver2/example_test.go rename to etcdserver/example_test.go diff --git a/etcdserver/package_stats.go b/etcdserver/package_stats.go deleted file mode 100644 index 1dcce1bda..000000000 --- a/etcdserver/package_stats.go +++ /dev/null @@ -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 -} diff --git a/etcdserver/participant.go b/etcdserver/participant.go deleted file mode 100644 index 30528c63c..000000000 --- a/etcdserver/participant.go +++ /dev/null @@ -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() -} diff --git a/etcdserver/peer.go b/etcdserver/peer.go deleted file mode 100644 index 0d045134a..000000000 --- a/etcdserver/peer.go +++ /dev/null @@ -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) -} diff --git a/etcdserver/peer_hub.go b/etcdserver/peer_hub.go deleted file mode 100644 index ed8dad68d..000000000 --- a/etcdserver/peer_hub.go +++ /dev/null @@ -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)) -} diff --git a/etcdserver/raft_follower_stats.go b/etcdserver/raft_follower_stats.go deleted file mode 100644 index fa769812e..000000000 --- a/etcdserver/raft_follower_stats.go +++ /dev/null @@ -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++ -} diff --git a/etcdserver/raft_handler.go b/etcdserver/raft_handler.go deleted file mode 100644 index 22dc67dd9..000000000 --- a/etcdserver/raft_handler.go +++ /dev/null @@ -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 - } -} diff --git a/etcdserver/raft_server_stats.go b/etcdserver/raft_server_stats.go deleted file mode 100644 index 9995b145f..000000000 --- a/etcdserver/raft_server_stats.go +++ /dev/null @@ -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++ -} diff --git a/etcdserver2/server.go b/etcdserver/server.go similarity index 98% rename from etcdserver2/server.go rename to etcdserver/server.go index 2a6828d06..778ddc52f 100644 --- a/etcdserver2/server.go +++ b/etcdserver/server.go @@ -5,7 +5,7 @@ import ( "time" "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/raftpb" "github.com/coreos/etcd/store" diff --git a/etcdserver2/server_test.go b/etcdserver/server_test.go similarity index 97% rename from etcdserver2/server_test.go rename to etcdserver/server_test.go index ed02ec1f8..542aae80c 100644 --- a/etcdserver2/server_test.go +++ b/etcdserver/server_test.go @@ -5,9 +5,10 @@ import ( "reflect" "testing" "time" + "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/raftpb" "github.com/coreos/etcd/store" diff --git a/etcdserver/standby.go b/etcdserver/standby.go deleted file mode 100644 index 9a555299c..000000000 --- a/etcdserver/standby.go +++ /dev/null @@ -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") -} diff --git a/etcdserver/stats_queue.go b/etcdserver/stats_queue.go deleted file mode 100644 index 8961db7ce..000000000 --- a/etcdserver/stats_queue.go +++ /dev/null @@ -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 -} diff --git a/etcdserver/v2_admin.go b/etcdserver/v2_admin.go deleted file mode 100644 index 3967c7c0c..000000000 --- a/etcdserver/v2_admin.go +++ /dev/null @@ -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 -} diff --git a/etcdserver/v2_apply.go b/etcdserver/v2_apply.go deleted file mode 100644 index 03c28c85d..000000000 --- a/etcdserver/v2_apply.go +++ /dev/null @@ -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 -} diff --git a/etcdserver/v2_client.go b/etcdserver/v2_client.go deleted file mode 100644 index 7ddc79d71..000000000 --- a/etcdserver/v2_client.go +++ /dev/null @@ -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) -} diff --git a/etcdserver/v2_http.go b/etcdserver/v2_http.go deleted file mode 100644 index 5e1adde35..000000000 --- a/etcdserver/v2_http.go +++ /dev/null @@ -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 -} diff --git a/etcdserver/v2_http_delete.go b/etcdserver/v2_http_delete.go deleted file mode 100644 index 0b1bcbaeb..000000000 --- a/etcdserver/v2_http_delete.go +++ /dev/null @@ -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 -} diff --git a/etcdserver/v2_http_endpoint_test.go b/etcdserver/v2_http_endpoint_test.go deleted file mode 100644 index 2e6c1d2e1..000000000 --- a/etcdserver/v2_http_endpoint_test.go +++ /dev/null @@ -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] } diff --git a/etcdserver/v2_http_get.go b/etcdserver/v2_http_get.go deleted file mode 100644 index 07f47eb4b..000000000 --- a/etcdserver/v2_http_get.go +++ /dev/null @@ -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) -} diff --git a/etcdserver/v2_http_kv_test.go b/etcdserver/v2_http_kv_test.go deleted file mode 100644 index 39d15bc1c..000000000 --- a/etcdserver/v2_http_kv_test.go +++ /dev/null @@ -1,1080 +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" - "io/ioutil" - "net" - "net/http" - "net/url" - "reflect" - "strings" - "testing" - "time" -) - -func TestV2Set(t *testing.T) { - cl := testCluster{Size: 1} - cl.Start() - defer cl.Destroy() - - u := cl.URL(0) - tc := NewTestClient() - v := url.Values{} - v.Set("value", "bar") - - tests := []struct { - relativeURL string - value url.Values - wStatus int - w string - }{ - { - "/v2/keys/foo/bar", - v, - http.StatusCreated, - `{"action":"set","node":{"key":"/foo/bar","value":"bar","modifiedIndex":2,"createdIndex":2}}`, - }, - { - "/v2/keys/foodir?dir=true", - url.Values{}, - http.StatusCreated, - `{"action":"set","node":{"key":"/foodir","dir":true,"modifiedIndex":3,"createdIndex":3}}`, - }, - { - "/v2/keys/fooempty", - url.Values(map[string][]string{"value": {""}}), - http.StatusCreated, - `{"action":"set","node":{"key":"/fooempty","value":"","modifiedIndex":4,"createdIndex":4}}`, - }, - } - - for i, tt := range tests { - resp, err := tc.PutForm(fmt.Sprintf("%s%s", u, tt.relativeURL), tt.value) - if err != nil { - t.Errorf("#%d: err = %v, want nil", i, err) - } - g := string(tc.ReadBody(resp)) - if g != tt.w { - t.Errorf("#%d: body = %v, want %v", i, g, tt.w) - } - if resp.StatusCode != tt.wStatus { - t.Errorf("#%d: status = %d, want %d", i, resp.StatusCode, tt.wStatus) - } - } -} - -func TestV2CreateUpdate(t *testing.T) { - cl := testCluster{Size: 1} - cl.Start() - defer cl.Destroy() - - u := cl.URL(0) - tc := NewTestClient() - - tests := []struct { - relativeURL string - value url.Values - wStatus int - w map[string]interface{} - }{ - // key with ttl - { - "/v2/keys/ttl/foo", - url.Values(map[string][]string{"value": {"XXX"}, "ttl": {"20"}}), - http.StatusCreated, - map[string]interface{}{ - "node": map[string]interface{}{ - "value": "XXX", - "ttl": float64(20), - }, - }, - }, - // key with bad ttl - { - "/v2/keys/ttl/foo", - url.Values(map[string][]string{"value": {"XXX"}, "ttl": {"bad_ttl"}}), - http.StatusBadRequest, - map[string]interface{}{ - "errorCode": float64(202), - "message": "The given TTL in POST form is not a number", - "cause": "Update", - }, - }, - // create key - { - "/v2/keys/create/foo", - url.Values(map[string][]string{"value": {"XXX"}, "prevExist": {"false"}}), - http.StatusCreated, - map[string]interface{}{ - "node": map[string]interface{}{ - "value": "XXX", - }, - }, - }, - // created key failed - { - "/v2/keys/create/foo", - url.Values(map[string][]string{"value": {"XXX"}, "prevExist": {"false"}}), - http.StatusPreconditionFailed, - map[string]interface{}{ - "errorCode": float64(105), - "message": "Key already exists", - "cause": "/create/foo", - }, - }, - // update the newly created key with ttl - { - "/v2/keys/create/foo", - url.Values(map[string][]string{"value": {"YYY"}, "prevExist": {"true"}, "ttl": {"20"}}), - http.StatusOK, - map[string]interface{}{ - "node": map[string]interface{}{ - "value": "YYY", - "ttl": float64(20), - }, - "action": "update", - }, - }, - // update the ttl to none - { - "/v2/keys/create/foo", - url.Values(map[string][]string{"value": {"ZZZ"}, "prevExist": {"true"}}), - http.StatusOK, - map[string]interface{}{ - "node": map[string]interface{}{ - "value": "ZZZ", - }, - "action": "update", - }, - }, - // update on a non-existing key - { - "/v2/keys/nonexist", - url.Values(map[string][]string{"value": {"XXX"}, "prevExist": {"true"}}), - http.StatusNotFound, - map[string]interface{}{ - "errorCode": float64(100), - "message": "Key not found", - "cause": "/nonexist", - }, - }, - } - - for i, tt := range tests { - resp, _ := tc.PutForm(fmt.Sprintf("%s%s", u, tt.relativeURL), tt.value) - if resp.StatusCode != tt.wStatus { - t.Errorf("#%d: status = %d, want %d", i, resp.StatusCode, tt.wStatus) - } - if err := checkBody(tc.ReadBodyJSON(resp), tt.w); err != nil { - t.Errorf("#%d: %v", i, err) - } - } -} - -func TestV2CAS(t *testing.T) { - cl := testCluster{Size: 1} - cl.Start() - defer cl.Destroy() - - u := cl.URL(0) - tc := NewTestClient() - - tests := []struct { - relativeURL string - value url.Values - wStatus int - w map[string]interface{} - }{ - { - "/v2/keys/cas/foo", - url.Values(map[string][]string{"value": {"XXX"}}), - http.StatusCreated, - nil, - }, - { - "/v2/keys/cas/foo", - url.Values(map[string][]string{"value": {"YYY"}, "prevIndex": {"2"}}), - http.StatusOK, - map[string]interface{}{ - "node": map[string]interface{}{ - "value": "YYY", - "modifiedIndex": float64(3), - }, - "action": "compareAndSwap", - }, - }, - { - "/v2/keys/cas/foo", - url.Values(map[string][]string{"value": {"YYY"}, "prevIndex": {"10"}}), - http.StatusPreconditionFailed, - map[string]interface{}{ - "errorCode": float64(101), - "message": "Compare failed", - "cause": "[10 != 3]", - "index": float64(3), - }, - }, - { - "/v2/keys/cas/foo", - url.Values(map[string][]string{"value": {"YYY"}, "prevIndex": {"bad_index"}}), - http.StatusBadRequest, - map[string]interface{}{ - "errorCode": float64(203), - "message": "The given index in POST form is not a number", - "cause": "CompareAndSwap", - }, - }, - { - "/v2/keys/cas/foo", - url.Values(map[string][]string{"value": {"ZZZ"}, "prevValue": {"YYY"}}), - http.StatusOK, - map[string]interface{}{ - "node": map[string]interface{}{ - "value": "ZZZ", - }, - "action": "compareAndSwap", - }, - }, - { - "/v2/keys/cas/foo", - url.Values(map[string][]string{"value": {"XXX"}, "prevValue": {"bad_value"}}), - http.StatusPreconditionFailed, - map[string]interface{}{ - "errorCode": float64(101), - "message": "Compare failed", - "cause": "[bad_value != ZZZ]", - }, - }, - // prevValue is required - { - "/v2/keys/cas/foo", - url.Values(map[string][]string{"value": {"XXX"}, "prevValue": {""}}), - http.StatusBadRequest, - map[string]interface{}{ - "errorCode": float64(201), - "message": "PrevValue is Required in POST form", - "cause": "CompareAndSwap", - }, - }, - { - "/v2/keys/cas/foo", - url.Values(map[string][]string{"value": {"XXX"}, "prevValue": {"bad_value"}, "prevIndex": {"100"}}), - http.StatusPreconditionFailed, - map[string]interface{}{ - "errorCode": float64(101), - "message": "Compare failed", - "cause": "[bad_value != ZZZ] [100 != 4]", - }, - }, - { - "/v2/keys/cas/foo", - url.Values(map[string][]string{"value": {"XXX"}, "prevValue": {"ZZZ"}, "prevIndex": {"100"}}), - http.StatusPreconditionFailed, - map[string]interface{}{ - "errorCode": float64(101), - "message": "Compare failed", - "cause": "[100 != 4]", - }, - }, - { - "/v2/keys/cas/foo", - url.Values(map[string][]string{"value": {"XXX"}, "prevValue": {"bad_value"}, "prevIndex": {"4"}}), - http.StatusPreconditionFailed, - map[string]interface{}{ - "errorCode": float64(101), - "message": "Compare failed", - "cause": "[bad_value != ZZZ]", - }, - }, - } - - for i, tt := range tests { - resp, _ := tc.PutForm(fmt.Sprintf("%s%s", u, tt.relativeURL), tt.value) - if resp.StatusCode != tt.wStatus { - t.Errorf("#%d: status = %d, want %d", i, resp.StatusCode, tt.wStatus) - } - if err := checkBody(tc.ReadBodyJSON(resp), tt.w); err != nil { - t.Errorf("#%d: %v", i, err) - } - } -} - -func TestV2Delete(t *testing.T) { - cl := testCluster{Size: 1} - cl.Start() - defer cl.Destroy() - - u := cl.URL(0) - tc := NewTestClient() - - v := url.Values{} - v.Set("value", "XXX") - resp, err := tc.PutForm(fmt.Sprintf("%s%s", u, "/v2/keys/foo"), v) - if err != nil { - t.Error(err) - } - resp.Body.Close() - resp, err = tc.PutForm(fmt.Sprintf("%s%s", u, "/v2/keys/emptydir?dir=true"), v) - if err != nil { - t.Error(err) - } - resp.Body.Close() - resp, err = tc.PutForm(fmt.Sprintf("%s%s", u, "/v2/keys/foodir/bar?dir=true"), v) - if err != nil { - t.Error(err) - } - resp.Body.Close() - - tests := []struct { - relativeURL string - wStatus int - w map[string]interface{} - }{ - { - "/v2/keys/foo", - http.StatusOK, - map[string]interface{}{ - "node": map[string]interface{}{ - "key": "/foo", - }, - "prevNode": map[string]interface{}{ - "key": "/foo", - "value": "XXX", - }, - "action": "delete", - }, - }, - { - "/v2/keys/emptydir", - http.StatusForbidden, - map[string]interface{}{ - "errorCode": float64(102), - "message": "Not a file", - "cause": "/emptydir", - }, - }, - { - "/v2/keys/emptydir?dir=true", - http.StatusOK, - nil, - }, - { - "/v2/keys/foodir?dir=true", - http.StatusForbidden, - map[string]interface{}{ - "errorCode": float64(108), - "message": "Directory not empty", - "cause": "/foodir", - }, - }, - { - "/v2/keys/foodir?recursive=true", - http.StatusOK, - map[string]interface{}{ - "node": map[string]interface{}{ - "key": "/foodir", - "dir": true, - }, - "prevNode": map[string]interface{}{ - "key": "/foodir", - "dir": true, - }, - "action": "delete", - }, - }, - } - - for i, tt := range tests { - resp, _ := tc.DeleteForm(fmt.Sprintf("%s%s", u, tt.relativeURL), nil) - if resp.StatusCode != tt.wStatus { - t.Errorf("#%d: status = %d, want %d", i, resp.StatusCode, tt.wStatus) - } - if err := checkBody(tc.ReadBodyJSON(resp), tt.w); err != nil { - t.Errorf("#%d: %v", i, err) - } - } -} - -func TestV2CAD(t *testing.T) { - cl := testCluster{Size: 1} - cl.Start() - defer cl.Destroy() - - u := cl.URL(0) - tc := NewTestClient() - - v := url.Values{} - v.Set("value", "XXX") - resp, err := tc.PutForm(fmt.Sprintf("%s%s", u, "/v2/keys/foo"), v) - if err != nil { - t.Error(err) - } - resp.Body.Close() - - resp, err = tc.PutForm(fmt.Sprintf("%s%s", u, "/v2/keys/foovalue"), v) - if err != nil { - t.Error(err) - } - resp.Body.Close() - - tests := []struct { - relativeURL string - wStatus int - w map[string]interface{} - }{ - { - "/v2/keys/foo?prevIndex=100", - http.StatusPreconditionFailed, - map[string]interface{}{ - "errorCode": float64(101), - "message": "Compare failed", - "cause": "[100 != 2]", - }, - }, - { - "/v2/keys/foo?prevIndex=bad_index", - http.StatusBadRequest, - map[string]interface{}{ - "errorCode": float64(203), - "message": "The given index in POST form is not a number", - "cause": "CompareAndDelete", - }, - }, - { - "/v2/keys/foo?prevIndex=2", - http.StatusOK, - map[string]interface{}{ - "node": map[string]interface{}{ - "key": "/foo", - "modifiedIndex": float64(4), - }, - "action": "compareAndDelete", - }, - }, - { - "/v2/keys/foovalue?prevValue=YYY", - http.StatusPreconditionFailed, - map[string]interface{}{ - "errorCode": float64(101), - "message": "Compare failed", - "cause": "[YYY != XXX]", - }, - }, - { - "/v2/keys/foovalue?prevValue=", - http.StatusBadRequest, - map[string]interface{}{ - "errorCode": float64(201), - "message": "PrevValue is Required in POST form", - "cause": "CompareAndDelete", - }, - }, - { - "/v2/keys/foovalue?prevValue=XXX", - http.StatusOK, - map[string]interface{}{ - "node": map[string]interface{}{ - "key": "/foovalue", - "modifiedIndex": float64(5), - }, - "action": "compareAndDelete", - }, - }, - } - - for i, tt := range tests { - resp, _ := tc.DeleteForm(fmt.Sprintf("%s%s", u, tt.relativeURL), nil) - if resp.StatusCode != tt.wStatus { - t.Errorf("#%d: status = %d, want %d", i, resp.StatusCode, tt.wStatus) - } - if err := checkBody(tc.ReadBodyJSON(resp), tt.w); err != nil { - t.Errorf("#%d: %v", i, err) - } - } -} - -func TestV2Unique(t *testing.T) { - cl := testCluster{Size: 1} - cl.Start() - defer cl.Destroy() - - u := cl.URL(0) - tc := NewTestClient() - - tests := []struct { - relativeURL string - value url.Values - wStatus int - w map[string]interface{} - }{ - { - "/v2/keys/foo", - url.Values(map[string][]string{"value": {"XXX"}}), - http.StatusCreated, - map[string]interface{}{ - "node": map[string]interface{}{ - "key": "/foo/2", - "value": "XXX", - }, - "action": "create", - }, - }, - { - "/v2/keys/foo", - url.Values(map[string][]string{"value": {"XXX"}}), - http.StatusCreated, - map[string]interface{}{ - "node": map[string]interface{}{ - "key": "/foo/3", - "value": "XXX", - }, - "action": "create", - }, - }, - { - "/v2/keys/bar", - url.Values(map[string][]string{"value": {"XXX"}}), - http.StatusCreated, - map[string]interface{}{ - "node": map[string]interface{}{ - "key": "/bar/4", - "value": "XXX", - }, - "action": "create", - }, - }, - } - - for i, tt := range tests { - resp, _ := tc.PostForm(fmt.Sprintf("%s%s", u, tt.relativeURL), tt.value) - if resp.StatusCode != tt.wStatus { - t.Errorf("#%d: status = %d, want %d", i, resp.StatusCode, tt.wStatus) - } - if err := checkBody(tc.ReadBodyJSON(resp), tt.w); err != nil { - t.Errorf("#%d: %v", i, err) - } - } -} - -func TestV2Get(t *testing.T) { - cl := testCluster{Size: 1} - cl.Start() - defer cl.Destroy() - - u := cl.URL(0) - tc := NewTestClient() - - v := url.Values{} - v.Set("value", "XXX") - resp, err := tc.PutForm(fmt.Sprintf("%s%s", u, "/v2/keys/foo/bar/zar"), v) - if err != nil { - t.Error(err) - } - resp.Body.Close() - - tests := []struct { - relativeURL string - wStatus int - w map[string]interface{} - }{ - { - "/v2/keys/foo/bar/zar", - http.StatusOK, - map[string]interface{}{ - "node": map[string]interface{}{ - "key": "/foo/bar/zar", - "value": "XXX", - }, - "action": "get", - }, - }, - { - "/v2/keys/foo", - http.StatusOK, - map[string]interface{}{ - "node": map[string]interface{}{ - "key": "/foo", - "dir": true, - "nodes": []interface{}{ - map[string]interface{}{ - "key": "/foo/bar", - "dir": true, - "createdIndex": float64(2), - "modifiedIndex": float64(2), - }, - }, - }, - "action": "get", - }, - }, - { - "/v2/keys/foo?recursive=true", - http.StatusOK, - map[string]interface{}{ - "node": map[string]interface{}{ - "key": "/foo", - "dir": true, - "nodes": []interface{}{ - map[string]interface{}{ - "key": "/foo/bar", - "dir": true, - "createdIndex": float64(2), - "modifiedIndex": float64(2), - "nodes": []interface{}{ - map[string]interface{}{ - "key": "/foo/bar/zar", - "value": "XXX", - "createdIndex": float64(2), - "modifiedIndex": float64(2), - }, - }, - }, - }, - }, - "action": "get", - }, - }, - } - - for i, tt := range tests { - resp, _ := tc.Get(fmt.Sprintf("%s%s", u, tt.relativeURL)) - if resp.StatusCode != tt.wStatus { - t.Errorf("#%d: status = %d, want %d", i, resp.StatusCode, tt.wStatus) - } - if resp.Header.Get("Content-Type") != "application/json" { - t.Errorf("#%d: header = %v, want %v", resp.Header.Get("Content-Type"), "application/json") - } - if err := checkBody(tc.ReadBodyJSON(resp), tt.w); err != nil { - t.Errorf("#%d: %v", i, err) - } - } -} - -func TestConsistentGet(t *testing.T) { - defer afterTest(t) - - noredirect := func(req *http.Request, via []*http.Request) error { - return errors.New("no redirect") - } - - c := &testCluster{Size: 3} - c.Start() - defer c.Destroy() - - u := fmt.Sprintf("%s%s", c.URL(1), "/v2/keys/foo?consistent=true") - ru := fmt.Sprintf("%s%s", c.URL(0), "/v2/keys/foo?consistent=true") - tc := testHttpClient{&http.Client{CheckRedirect: noredirect}} - resp, _ := tc.Get(u) - if resp.StatusCode != http.StatusTemporaryRedirect { - t.Errorf("status = %d, want %d", resp.StatusCode, http.StatusTemporaryRedirect) - } - location, err := resp.Location() - if err != nil { - t.Errorf("err = %v, want nil", err) - } - if location.String() != ru { - t.Errorf("location = %v, want %v", location.String(), ru) - } - resp.Body.Close() -} - -func TestV2QuorumGet(t *testing.T) { - cl := testCluster{Size: 1} - cl.Start() - defer cl.Destroy() - - u := cl.URL(0) - tc := NewTestClient() - - v := url.Values{} - v.Set("value", "XXX") - resp, err := tc.PutForm(fmt.Sprintf("%s%s", u, "/v2/keys/foo/bar/zar?quorum=true"), v) - if err != nil { - t.Error(err) - } - resp.Body.Close() - - tests := []struct { - relativeURL string - wStatus int - w map[string]interface{} - }{ - { - "/v2/keys/foo/bar/zar", - http.StatusOK, - map[string]interface{}{ - "node": map[string]interface{}{ - "key": "/foo/bar/zar", - "value": "XXX", - }, - "action": "get", - }, - }, - { - "/v2/keys/foo", - http.StatusOK, - map[string]interface{}{ - "node": map[string]interface{}{ - "key": "/foo", - "dir": true, - "nodes": []interface{}{ - map[string]interface{}{ - "key": "/foo/bar", - "dir": true, - "createdIndex": float64(2), - "modifiedIndex": float64(2), - }, - }, - }, - "action": "get", - }, - }, - { - "/v2/keys/foo?recursive=true", - http.StatusOK, - map[string]interface{}{ - "node": map[string]interface{}{ - "key": "/foo", - "dir": true, - "nodes": []interface{}{ - map[string]interface{}{ - "key": "/foo/bar", - "dir": true, - "createdIndex": float64(2), - "modifiedIndex": float64(2), - "nodes": []interface{}{ - map[string]interface{}{ - "key": "/foo/bar/zar", - "value": "XXX", - "createdIndex": float64(2), - "modifiedIndex": float64(2), - }, - }, - }, - }, - }, - "action": "get", - }, - }, - } - - for i, tt := range tests { - resp, _ := tc.Get(fmt.Sprintf("%s%s", u, tt.relativeURL)) - if resp.StatusCode != tt.wStatus { - t.Errorf("#%d: status = %d, want %d", i, resp.StatusCode, tt.wStatus) - } - if resp.Header.Get("Content-Type") != "application/json" { - t.Errorf("#%d: header = %v, want %v", resp.Header.Get("Content-Type"), "application/json") - } - if err := checkBody(tc.ReadBodyJSON(resp), tt.w); err != nil { - t.Errorf("#%d: %v", i, err) - } - } -} - -func TestV2Watch(t *testing.T) { - cl := testCluster{Size: 1} - cl.Start() - defer cl.Destroy() - - u := cl.URL(0) - tc := NewTestClient() - - var watchResp *http.Response - c := make(chan bool) - go func() { - watchResp, _ = tc.Get(fmt.Sprintf("%s%s", u, "/v2/keys/foo/bar?wait=true")) - c <- true - }() - - // Make sure response didn't fire early. - time.Sleep(1 * time.Millisecond) - - // Set a value. - v := url.Values{} - v.Set("value", "XXX") - resp, _ := tc.PutForm(fmt.Sprintf("%s%s", u, "/v2/keys/foo/bar"), v) - resp.Body.Close() - - select { - case <-c: - case <-time.After(time.Millisecond): - t.Fatal("cannot get watch result") - } - - body := tc.ReadBodyJSON(watchResp) - w := map[string]interface{}{ - "node": map[string]interface{}{ - "key": "/foo/bar", - "value": "XXX", - "modifiedIndex": float64(2), - }, - "action": "set", - } - - if err := checkBody(body, w); err != nil { - t.Error(err) - } -} - -func TestV2WatchWithIndex(t *testing.T) { - cl := testCluster{Size: 1} - cl.Start() - defer cl.Destroy() - - u := cl.URL(0) - tc := NewTestClient() - - var body map[string]interface{} - c := make(chan bool, 1) - go func() { - resp, _ := tc.Get(fmt.Sprintf("%s%s", u, "/v2/keys/foo/bar?wait=true&waitIndex=3")) - body = tc.ReadBodyJSON(resp) - c <- true - }() - - select { - case <-c: - t.Fatal("should not get the watch result") - case <-time.After(time.Millisecond): - } - - // Set a value (before given index). - v := url.Values{} - v.Set("value", "XXX") - resp, _ := tc.PutForm(fmt.Sprintf("%s%s", u, "/v2/keys/foo/bar"), v) - resp.Body.Close() - - select { - case <-c: - t.Fatal("should not get the watch result") - case <-time.After(time.Millisecond): - } - - // Set a value (before given index). - resp, _ = tc.PutForm(fmt.Sprintf("%s%s", u, "/v2/keys/foo/bar"), v) - resp.Body.Close() - - select { - case <-c: - case <-time.After(time.Millisecond): - t.Fatal("cannot get watch result") - } - - w := map[string]interface{}{ - "node": map[string]interface{}{ - "key": "/foo/bar", - "value": "XXX", - "modifiedIndex": float64(3), - }, - "action": "set", - } - if err := checkBody(body, w); err != nil { - t.Error(err) - } -} - -func TestV2WatchKeyInDir(t *testing.T) { - cl := testCluster{Size: 1} - cl.Start() - defer cl.Destroy() - - u := cl.URL(0) - tc := NewTestClient() - - var body map[string]interface{} - c := make(chan bool) - - // Set a value (before given index). - v := url.Values{} - v.Set("dir", "true") - v.Set("ttl", "1") - resp, _ := tc.PutForm(fmt.Sprintf("%s%s", u, "/v2/keys/keyindir"), v) - resp.Body.Close() - - // Set a value (before given index). - v = url.Values{} - v.Set("value", "XXX") - resp, _ = tc.PutForm(fmt.Sprintf("%s%s", u, "/v2/keys/keyindir/bar"), v) - resp.Body.Close() - - go func() { - resp, _ := tc.Get(fmt.Sprintf("%s%s", u, "/v2/keys/keyindir/bar?wait=true")) - body = tc.ReadBodyJSON(resp) - c <- true - }() - - select { - case <-c: - case <-time.After(time.Millisecond * 1500): - t.Fatal("cannot get watch result") - } - - w := map[string]interface{}{ - "node": map[string]interface{}{ - "key": "/keyindir", - }, - "action": "expire", - } - if err := checkBody(body, w); err != nil { - t.Error(err) - } -} - -func TestV2Head(t *testing.T) { - cl := testCluster{Size: 1} - cl.Start() - defer cl.Destroy() - - u := cl.URL(0) - tc := NewTestClient() - - v := url.Values{} - v.Set("value", "XXX") - fullURL := fmt.Sprintf("%s%s", u, "/v2/keys/foo/bar") - resp, _ := tc.Head(fullURL) - resp.Body.Close() - if resp.StatusCode != http.StatusNotFound { - t.Errorf("status = %d, want %d", resp.StatusCode, http.StatusNotFound) - } - if resp.ContentLength != -1 { - t.Errorf("ContentLength = %d, want -1", resp.ContentLength) - } - - resp, _ = tc.PutForm(fullURL, v) - resp.Body.Close() - - resp, _ = tc.Head(fullURL) - resp.Body.Close() - if resp.StatusCode != http.StatusOK { - t.Errorf("status = %d, want %d", resp.StatusCode, http.StatusOK) - } - if resp.ContentLength != -1 { - t.Errorf("ContentLength = %d, want -1", resp.ContentLength) - } -} - -func checkBody(body map[string]interface{}, w map[string]interface{}) error { - if body["node"] != nil { - if w["node"] != nil { - wn := w["node"].(map[string]interface{}) - n := body["node"].(map[string]interface{}) - for k := range n { - if wn[k] == nil { - delete(n, k) - } - } - body["node"] = n - } - if w["prevNode"] != nil { - wn := w["prevNode"].(map[string]interface{}) - n := body["prevNode"].(map[string]interface{}) - for k := range n { - if wn[k] == nil { - delete(n, k) - } - } - body["prevNode"] = n - } - } - for k, v := range w { - g := body[k] - if !reflect.DeepEqual(g, v) { - return fmt.Errorf("%v = %+v, want %+v", k, g, v) - } - } - return nil -} - -type testHttpClient struct { - *http.Client -} - -// Creates a new HTTP client with KeepAlive disabled. -func NewTestClient() *testHttpClient { - tr := &http.Transport{ - Dial: (&net.Dialer{Timeout: time.Second}).Dial, - DisableKeepAlives: true, - } - return &testHttpClient{&http.Client{Transport: tr}} -} - -// Reads the body from the response and closes it. -func (t *testHttpClient) 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 (t *testHttpClient) ReadBodyJSON(resp *http.Response) map[string]interface{} { - m := make(map[string]interface{}) - b := t.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 (t *testHttpClient) Head(url string) (*http.Response, error) { - return t.send("HEAD", url, "application/json", nil) -} - -func (t *testHttpClient) Get(url string) (*http.Response, error) { - return t.send("GET", url, "application/json", nil) -} - -func (t *testHttpClient) Post(url string, bodyType string, body io.Reader) (*http.Response, error) { - return t.send("POST", url, bodyType, body) -} - -func (t *testHttpClient) PostForm(url string, data url.Values) (*http.Response, error) { - return t.Post(url, "application/x-www-form-urlencoded", strings.NewReader(data.Encode())) -} - -func (t *testHttpClient) Put(url string, bodyType string, body io.Reader) (*http.Response, error) { - return t.send("PUT", url, bodyType, body) -} - -func (t *testHttpClient) PutForm(url string, data url.Values) (*http.Response, error) { - return t.Put(url, "application/x-www-form-urlencoded", strings.NewReader(data.Encode())) -} - -func (t *testHttpClient) Delete(url string, bodyType string, body io.Reader) (*http.Response, error) { - return t.send("DELETE", url, bodyType, body) -} - -func (t *testHttpClient) DeleteForm(url string, data url.Values) (*http.Response, error) { - return t.Delete(url, "application/x-www-form-urlencoded", strings.NewReader(data.Encode())) -} - -func (t *testHttpClient) send(method string, url string, bodyType string, body io.Reader) (*http.Response, error) { - req, err := http.NewRequest(method, url, body) - if err != nil { - return nil, err - } - req.Header.Set("Content-Type", bodyType) - return t.Do(req) -} diff --git a/etcdserver/v2_http_post.go b/etcdserver/v2_http_post.go deleted file mode 100644 index 3abb57162..000000000 --- a/etcdserver/v2_http_post.go +++ /dev/null @@ -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 -} diff --git a/etcdserver/v2_http_put.go b/etcdserver/v2_http_put.go deleted file mode 100644 index 3481f1527..000000000 --- a/etcdserver/v2_http_put.go +++ /dev/null @@ -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 -} diff --git a/etcdserver/v2_raft.go b/etcdserver/v2_raft.go deleted file mode 100644 index d44639ef9..000000000 --- a/etcdserver/v2_raft.go +++ /dev/null @@ -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) -} diff --git a/etcdserver/v2_store.go b/etcdserver/v2_store.go deleted file mode 100644 index ab40bbf78..000000000 --- a/etcdserver/v2_store.go +++ /dev/null @@ -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") - } -} diff --git a/etcdserver/v2_store_cmd.pb.go b/etcdserver/v2_store_cmd.pb.go deleted file mode 100644 index 605e539ba..000000000 --- a/etcdserver/v2_store_cmd.pb.go +++ /dev/null @@ -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 -} diff --git a/etcdserver/v2_store_cmd.proto b/etcdserver/v2_store_cmd.proto deleted file mode 100644 index fd81656fe..000000000 --- a/etcdserver/v2_store_cmd.proto +++ /dev/null @@ -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; -} diff --git a/etcdserver/v2_usage.go b/etcdserver/v2_usage.go deleted file mode 100644 index e11ea5911..000000000 --- a/etcdserver/v2_usage.go +++ /dev/null @@ -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 - etcd -name [-data-dir=] - 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 to configuration file. - -name= Name of this node in the etcd cluster. - -data-dir= Path to the data directory. - -cors= Comma-separated list of CORS origins. - -v Enabled verbose logging. - -vv Enabled very verbose logging. - -Cluster Configuration Options: - -discovery= Discovery service used to find a peer list. - -peers-file= Path to a file containing the peer list. - -peers=, Comma-separated list of peers. The members - should match the peer's '-peer-addr' flag. - -Client Communication Options: - -addr= The public host:port used for client communication. - -bind-addr= The listening host:port used for client communication. - -ca-file= Path to the client CA file. - -cert-file= Path to the client cert file. - -key-file= Path to the client key file. - -Peer Communication Options: - -peer-addr= The public host:port used for peer communication. - -peer-bind-addr= The listening host:port used for peer communication. - -peer-ca-file= Path to the peer CA file. - -peer-cert-file= Path to the peer cert file. - -peer-key-file= Path to the peer key file. - -peer-heartbeat-interval=