etcdserver: Calculate IDs for nodes solely on PeerURLs

Removes the notion of name being anything more than advisory or
command-line grouping, and adds checks for bootstrapping the command
line. IDs are consistent if the URLs are consistent.
This commit is contained in:
Barak Michener 2014-10-21 13:25:34 -04:00
parent e475388bc0
commit 456d1ebcae
9 changed files with 90 additions and 45 deletions

View File

@ -43,16 +43,6 @@ func (c Cluster) FindID(id uint64) *Member {
return c.members[id] return c.members[id]
} }
func (c Cluster) FindName(name string) *Member {
for _, m := range c.members {
if m.Name == name {
return m
}
}
return nil
}
func (c Cluster) Add(m Member) error { func (c Cluster) Add(m Member) error {
if c.FindID(m.ID) != nil { if c.FindID(m.ID) != nil {
return fmt.Errorf("Member exists with identical ID %v", m) return fmt.Errorf("Member exists with identical ID %v", m)

View File

@ -137,7 +137,7 @@ func TestClusterFind(t *testing.T) {
c := NewCluster() c := NewCluster()
c.AddSlice(tt.mems) c.AddSlice(tt.mems)
m := c.FindName(tt.name) m := c.FindID(tt.id)
if m == nil && !tt.match { if m == nil && !tt.match {
continue continue
} }

View File

@ -27,7 +27,7 @@ import (
// ServerConfig holds the configuration of etcd as taken from the command line or discovery. // ServerConfig holds the configuration of etcd as taken from the command line or discovery.
type ServerConfig struct { type ServerConfig struct {
Name string LocalMember Member
DiscoveryURL string DiscoveryURL string
ClientURLs types.URLs ClientURLs types.URLs
DataDir string DataDir string
@ -45,11 +45,16 @@ func (c *ServerConfig) VerifyBootstrapConfig() error {
} }
// Make sure the cluster at least contains the local server. // Make sure the cluster at least contains the local server.
m := c.Cluster.FindName(c.Name) isOk := false
if m == nil { for _, m := range c.Cluster.members {
return fmt.Errorf("could not find name %v in cluster", c.Name) if m.ID == c.LocalMember.ID {
isOk = true
} }
if m.ID == raft.None { }
if !isOk {
return fmt.Errorf("couldn't find local ID in cluster config")
}
if c.LocalMember.ID == raft.None {
return fmt.Errorf("could not use %x as member id", raft.None) return fmt.Errorf("could not use %x as member id", raft.None)
} }
@ -58,7 +63,7 @@ func (c *ServerConfig) VerifyBootstrapConfig() error {
for _, m := range c.Cluster.Members() { for _, m := range c.Cluster.Members() {
for _, url := range m.PeerURLs { for _, url := range m.PeerURLs {
if urlMap[url] { if urlMap[url] {
return fmt.Errorf("duplicate url %v in server config", url) return fmt.Errorf("duplicate url %v in cluster config", url)
} }
urlMap[url] = true urlMap[url] = true
} }
@ -70,7 +75,7 @@ func (c *ServerConfig) WALDir() string { return path.Join(c.DataDir, "wal") }
func (c *ServerConfig) SnapDir() string { return path.Join(c.DataDir, "snap") } func (c *ServerConfig) SnapDir() string { return path.Join(c.DataDir, "snap") }
func (c *ServerConfig) ID() uint64 { return c.Cluster.FindName(c.Name).ID } func (c *ServerConfig) ID() uint64 { return c.LocalMember.ID }
func (c *ServerConfig) ShouldDiscover() bool { func (c *ServerConfig) ShouldDiscover() bool {
return c.DiscoveryURL != "" return c.DiscoveryURL != ""

View File

@ -45,15 +45,22 @@ func TestBootstrapConfigVerify(t *testing.T) {
for i, tt := range tests { for i, tt := range tests {
cluster := &Cluster{} cluster := &Cluster{}
cluster.Set(tt.clusterSetting) err := cluster.Set(tt.clusterSetting)
if err != nil && tt.shouldError {
continue
}
cfg := ServerConfig{ cfg := ServerConfig{
Name: "node1", LocalMember: Member{
ID: 0x7350a9cd4dc16f76,
},
DiscoveryURL: tt.disc, DiscoveryURL: tt.disc,
Cluster: cluster, Cluster: cluster,
ClusterState: tt.clst, ClusterState: tt.clst,
} }
err := cfg.VerifyBootstrapConfig() err = cfg.VerifyBootstrapConfig()
if (err == nil) && tt.shouldError { if (err == nil) && tt.shouldError {
t.Errorf("%#v", *cluster)
t.Errorf("#%d: Got no error where one was expected", i) t.Errorf("#%d: Got no error where one was expected", i)
} }
if (err != nil) && !tt.shouldError { if (err != nil) && !tt.shouldError {

View File

@ -22,6 +22,7 @@ import (
"fmt" "fmt"
"log" "log"
"path" "path"
"sort"
"strconv" "strconv"
"time" "time"
@ -54,7 +55,8 @@ func newMember(name string, peerURLs types.URLs, now *time.Time) *Member {
Attributes: Attributes{Name: name}, Attributes: Attributes{Name: name},
} }
b := []byte(m.Name) var b []byte
sort.Strings(m.PeerURLs)
for _, p := range m.PeerURLs { for _, p := range m.PeerURLs {
b = append(b, []byte(p)...) b = append(b, []byte(p)...)
} }
@ -68,6 +70,10 @@ func newMember(name string, peerURLs types.URLs, now *time.Time) *Member {
return m return m
} }
func NewMemberFromURLs(name string, urls types.URLs) *Member {
return newMember(name, urls, nil)
}
func (m Member) storeKey() string { func (m Member) storeKey() string {
return path.Join(storeMembersPrefix, idAsHex(m.ID)) return path.Join(storeMembersPrefix, idAsHex(m.ID))
} }

View File

@ -35,8 +35,15 @@ func TestMemberTime(t *testing.T) {
mem *Member mem *Member
id uint64 id uint64
}{ }{
{newMember("mem1", []url.URL{{Scheme: "http", Host: "10.0.0.8:2379"}}, nil), 11240395089494390470}, {newMember("mem1", []url.URL{{Scheme: "http", Host: "10.0.0.8:2379"}}, nil), 14544069596553697298},
{newMember("mem1", []url.URL{{Scheme: "http", Host: "10.0.0.1:2379"}}, timeParse("1984-12-23T15:04:05Z")), 5483967913615174889}, // Same ID, different name (names shouldn't matter)
{newMember("memfoo", []url.URL{{Scheme: "http", Host: "10.0.0.8:2379"}}, nil), 14544069596553697298},
// Same ID, different Time
{newMember("mem1", []url.URL{{Scheme: "http", Host: "10.0.0.8:2379"}}, timeParse("1984-12-23T15:04:05Z")), 2448790162483548276},
{newMember("mem1", []url.URL{{Scheme: "http", Host: "10.0.0.1:2379"}}, timeParse("1984-12-23T15:04:05Z")), 1466075294948436910},
// Order shouldn't matter
{newMember("mem1", []url.URL{{Scheme: "http", Host: "10.0.0.1:2379"}, {Scheme: "http", Host: "10.0.0.2:2379"}}, nil), 16552244735972308939},
{newMember("mem1", []url.URL{{Scheme: "http", Host: "10.0.0.2:2379"}, {Scheme: "http", Host: "10.0.0.1:2379"}}, nil), 16552244735972308939},
} }
for i, tt := range tests { for i, tt := range tests {
if tt.mem.ID != tt.id { if tt.mem.ID != tt.id {

View File

@ -213,7 +213,7 @@ func NewServer(cfg *ServerConfig) *EtcdServer {
cls := &clusterStore{Store: st, id: cid} cls := &clusterStore{Store: st, id: cid}
sstats := &stats.ServerStats{ sstats := &stats.ServerStats{
Name: cfg.Name, Name: cfg.LocalMember.Attributes.Name,
ID: idAsHex(cfg.ID()), ID: idAsHex(cfg.ID()),
} }
lstats := stats.NewLeaderStats(idAsHex(cfg.ID())) lstats := stats.NewLeaderStats(idAsHex(cfg.ID()))
@ -223,7 +223,7 @@ func NewServer(cfg *ServerConfig) *EtcdServer {
node: n, node: n,
id: id, id: id,
clusterID: cid, clusterID: cid,
attributes: Attributes{Name: cfg.Name, ClientURLs: cfg.ClientURLs.StringSlice()}, attributes: Attributes{Name: cfg.LocalMember.Attributes.Name, ClientURLs: cfg.ClientURLs.StringSlice()},
storage: struct { storage: struct {
*wal.WAL *wal.WAL
*snap.Snapshotter *snap.Snapshotter

View File

@ -89,18 +89,21 @@ func (c *cluster) Launch(t *testing.T) {
lns[i] = l lns[i] = l
bootstrapCfgs[i] = fmt.Sprintf("%s=%s", c.name(i), "http://"+l.Addr().String()) bootstrapCfgs[i] = fmt.Sprintf("%s=%s", c.name(i), "http://"+l.Addr().String())
} }
clusterCfg := &etcdserver.Cluster{} clusterCfg := etcdserver.NewCluster()
if err := clusterCfg.Set(strings.Join(bootstrapCfgs, ",")); err != nil { if err := clusterCfg.Set(strings.Join(bootstrapCfgs, ",")); err != nil {
t.Fatal(err) t.Fatal(err)
} }
var err error
for i := 0; i < c.Size; i++ { for i := 0; i < c.Size; i++ {
m := member{} m := member{}
m.PeerListeners = []net.Listener{lns[i]} m.PeerListeners = []net.Listener{lns[i]}
cln := newLocalListener(t) cln := newLocalListener(t)
m.ClientListeners = []net.Listener{cln} m.ClientListeners = []net.Listener{cln}
m.Name = c.name(i) listenUrls, err := types.NewURLs([]string{"http://" + lns[i].Addr().String()})
if err != nil {
t.Fatal(err)
}
m.LocalMember = *etcdserver.NewMemberFromURLs(c.name(i), listenUrls)
m.ClientURLs, err = types.NewURLs([]string{"http://" + cln.Addr().String()}) m.ClientURLs, err = types.NewURLs([]string{"http://" + cln.Addr().String()})
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)

61
main.go
View File

@ -131,12 +131,14 @@ func main() {
} }
pkg.SetFlagsFromEnv(fs) pkg.SetFlagsFromEnv(fs)
if err := setClusterForDiscovery(); err != nil {
log.Fatalf("etcd: %v", err) localMember, err := setupCluster()
if err != nil {
log.Fatalf("etcd: setupCluster returned error %v", err)
} }
if string(*proxyFlag) == flagtypes.ProxyValueOff { if string(*proxyFlag) == flagtypes.ProxyValueOff {
startEtcd() startEtcd(localMember)
} else { } else {
startProxy() startProxy()
} }
@ -146,7 +148,7 @@ func main() {
} }
// startEtcd launches the etcd server and HTTP handlers for client/server communication. // startEtcd launches the etcd server and HTTP handlers for client/server communication.
func startEtcd() { func startEtcd(self *etcdserver.Member) {
if *dir == "" { if *dir == "" {
*dir = fmt.Sprintf("%v_etcd_data", *name) *dir = fmt.Sprintf("%v_etcd_data", *name)
log.Printf("etcd: no data-dir provided, using default data-dir ./%s", *dir) log.Printf("etcd: no data-dir provided, using default data-dir ./%s", *dir)
@ -165,7 +167,7 @@ func startEtcd() {
log.Fatal(err.Error()) log.Fatal(err.Error())
} }
cfg := &etcdserver.ServerConfig{ cfg := &etcdserver.ServerConfig{
Name: *name, LocalMember: *self,
ClientURLs: acurls, ClientURLs: acurls,
DataDir: *dir, DataDir: *dir,
SnapCount: *snapCount, SnapCount: *snapCount,
@ -262,28 +264,53 @@ func startProxy() {
} }
} }
// setClusterForDiscovery sets cluster to a temporary value if you are using // setupCluster sets cluster to a temporary value if you are using
// the discovery. // discovery, or to the static configuration for bootstrapped clusters.
func setClusterForDiscovery() error { // Returns the local member on success.
func setupCluster() (*etcdserver.Member, error) {
set := make(map[string]bool) set := make(map[string]bool)
var m *etcdserver.Member
flag.Visit(func(f *flag.Flag) { flag.Visit(func(f *flag.Flag) {
set[f.Name] = true set[f.Name] = true
}) })
if set["discovery"] && set["initial-cluster"] { if set["discovery"] && set["initial-cluster"] {
return fmt.Errorf("both discovery and initial-cluster are set") return nil, fmt.Errorf("both discovery and bootstrap-config are set")
} }
if set["discovery"] { if set["discovery"] {
apurls, err := pkg.URLsFromFlags(fs, "advertise-peer-urls", "peer-addr", peerTLSInfo) apurls, err := pkg.URLsFromFlags(fs, "advertise-peer-urls", "peer-addr", peerTLSInfo)
if err != nil { if err != nil {
return err return nil, err
} }
addrs := make([]string, len(apurls)) m = etcdserver.NewMemberFromURLs(*name, apurls)
for i := range apurls { cluster = etcdserver.NewCluster()
addrs[i] = fmt.Sprintf("%s=%s", *name, apurls[i].String()) cluster.Add(*m)
} return m, nil
if err := cluster.Set(strings.Join(addrs, ",")); err != nil { } else if set["initial-cluster"] {
return err // We're statically configured, and cluster has appropriately been set.
// Try to configure by indexing the static cluster by name.
if set["name"] {
for _, c := range cluster.Members() {
if c.Name == *name {
return c, nil
} }
} }
return nil log.Println("etcd: cannot find the passed name", *name, "in initial-cluster. Trying advertise-peer-urls")
}
// Try to configure by looking for a matching machine based on the peer urls.
if set["advertise-peer-urls"] {
apurls, err := pkg.URLsFromFlags(flag.CommandLine, "advertise-peer-urls", "addr", peerTLSInfo)
if err != nil {
return nil, err
}
m = etcdserver.NewMemberFromURLs(*name, apurls)
for _, c := range cluster.Members() {
if c.ID == m.ID {
return c, nil
}
}
log.Println("etcd: Could not find a matching peer for the local instance in initial-cluster.")
}
return nil, fmt.Errorf("etcd: Bootstrapping a static cluster, but local name or local peer urls are not defined")
}
return nil, fmt.Errorf("etcd: Flag configuration not set for discovery or initial-cluster")
} }