Merge pull request #1309 from jonboulle/1309_standard_id

standardize ID serialization
This commit is contained in:
Jonathan Boulle 2014-10-31 10:50:32 -07:00
commit c53e58e97c
18 changed files with 188 additions and 162 deletions

View File

@ -31,7 +31,7 @@ import (
"github.com/coreos/etcd/Godeps/_workspace/src/github.com/jonboulle/clockwork" "github.com/coreos/etcd/Godeps/_workspace/src/github.com/jonboulle/clockwork"
"github.com/coreos/etcd/client" "github.com/coreos/etcd/client"
"github.com/coreos/etcd/pkg/strutil" "github.com/coreos/etcd/pkg/types"
) )
var ( var (
@ -57,7 +57,7 @@ type Discoverer interface {
type discovery struct { type discovery struct {
cluster string cluster string
id uint64 id types.ID
config string config string
c client.KeysAPI c client.KeysAPI
retries uint retries uint
@ -95,7 +95,7 @@ func proxyFuncFromEnv() (func(*http.Request) (*url.URL, error), error) {
return http.ProxyURL(proxyURL), nil return http.ProxyURL(proxyURL), nil
} }
func New(durl string, id uint64, config string) (Discoverer, error) { func New(durl string, id types.ID, config string) (Discoverer, error) {
u, err := url.Parse(durl) u, err := url.Parse(durl)
if err != nil { if err != nil {
return nil, err return nil, err
@ -268,7 +268,7 @@ func (d *discovery) waitNodes(nodes client.Nodes, size int) (client.Nodes, error
} }
func (d *discovery) selfKey() string { func (d *discovery) selfKey() string {
return path.Join("/", d.cluster, strutil.IDAsHex(d.id)) return path.Join("/", d.cluster, d.id.String())
} }
func nodesToCluster(ns client.Nodes) string { func nodesToCluster(ns client.Nodes) string {

View File

@ -40,21 +40,21 @@ const (
) )
type ClusterInfo interface { type ClusterInfo interface {
ID() uint64 ID() types.ID
ClientURLs() []string ClientURLs() []string
// Members returns a slice of members sorted by their ID // Members returns a slice of members sorted by their ID
Members() []*Member Members() []*Member
Member(id uint64) *Member Member(id types.ID) *Member
} }
// Cluster is a list of Members that belong to the same raft cluster // Cluster is a list of Members that belong to the same raft cluster
type Cluster struct { type Cluster struct {
id uint64 id types.ID
token string token string
members map[uint64]*Member members map[types.ID]*Member
// removed contains the ids of removed members in the cluster. // removed contains the ids of removed members in the cluster.
// removed id cannot be reused. // removed id cannot be reused.
removed map[uint64]bool removed map[types.ID]bool
store store.Store store store.Store
} }
@ -119,7 +119,7 @@ func NewClusterFromStore(token string, st store.Store) *Cluster {
return c return c
} }
func NewClusterFromMembers(token string, id uint64, membs []*Member) *Cluster { func NewClusterFromMembers(token string, id types.ID, membs []*Member) *Cluster {
c := newCluster(token) c := newCluster(token)
c.id = id c.id = id
for _, m := range membs { for _, m := range membs {
@ -131,12 +131,12 @@ func NewClusterFromMembers(token string, id uint64, membs []*Member) *Cluster {
func newCluster(token string) *Cluster { func newCluster(token string) *Cluster {
return &Cluster{ return &Cluster{
token: token, token: token,
members: make(map[uint64]*Member), members: make(map[types.ID]*Member),
removed: make(map[uint64]bool), removed: make(map[types.ID]bool),
} }
} }
func (c Cluster) ID() uint64 { return c.id } func (c Cluster) ID() types.ID { return c.id }
func (c Cluster) Members() []*Member { func (c Cluster) Members() []*Member {
var sms SortableMemberSlice var sms SortableMemberSlice
@ -153,7 +153,7 @@ func (s SortableMemberSlice) Len() int { return len(s) }
func (s SortableMemberSlice) Less(i, j int) bool { return s[i].ID < s[j].ID } func (s SortableMemberSlice) Less(i, j int) bool { return s[i].ID < s[j].ID }
func (s SortableMemberSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] } func (s SortableMemberSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func (c *Cluster) Member(id uint64) *Member { func (c *Cluster) Member(id types.ID) *Member {
return c.members[id] return c.members[id]
} }
@ -172,16 +172,16 @@ func (c *Cluster) MemberByName(name string) *Member {
return memb return memb
} }
func (c Cluster) MemberIDs() []uint64 { func (c Cluster) MemberIDs() []types.ID {
var ids []uint64 var ids []types.ID
for _, m := range c.members { for _, m := range c.members {
ids = append(ids, m.ID) ids = append(ids, m.ID)
} }
sort.Sort(types.Uint64Slice(ids)) sort.Sort(types.IDSlice(ids))
return ids return ids
} }
func (c *Cluster) IsIDRemoved(id uint64) bool { func (c *Cluster) IsIDRemoved(id types.ID) bool {
return c.removed[id] return c.removed[id]
} }
@ -244,7 +244,7 @@ func (c *Cluster) ValidateAndAssignIDs(membs []*Member) error {
} }
omembs[i].ID = membs[i].ID omembs[i].ID = membs[i].ID
} }
c.members = make(map[uint64]*Member) c.members = make(map[types.ID]*Member)
for _, m := range omembs { for _, m := range omembs {
c.members[m.ID] = m c.members[m.ID] = m
} }
@ -255,13 +255,13 @@ func (c *Cluster) genID() {
mIDs := c.MemberIDs() mIDs := c.MemberIDs()
b := make([]byte, 8*len(mIDs)) b := make([]byte, 8*len(mIDs))
for i, id := range mIDs { for i, id := range mIDs {
binary.BigEndian.PutUint64(b[8*i:], id) binary.BigEndian.PutUint64(b[8*i:], uint64(id))
} }
hash := sha1.Sum(b) hash := sha1.Sum(b)
c.id = binary.BigEndian.Uint64(hash[:8]) c.id = types.ID(binary.BigEndian.Uint64(hash[:8]))
} }
func (c *Cluster) SetID(id uint64) { c.id = id } func (c *Cluster) SetID(id types.ID) { c.id = id }
func (c *Cluster) SetStore(st store.Store) { c.store = st } func (c *Cluster) SetStore(st store.Store) { c.store = st }
@ -289,7 +289,7 @@ func (c *Cluster) AddMember(m *Member) {
// RemoveMember removes a member from the store. // RemoveMember removes a member from the store.
// The given id MUST exist, or the function panics. // The given id MUST exist, or the function panics.
func (c *Cluster) RemoveMember(id uint64) { func (c *Cluster) RemoveMember(id types.ID) {
if _, err := c.store.Delete(memberStoreKey(id), true, true); err != nil { if _, err := c.store.Delete(memberStoreKey(id), true, true); err != nil {
log.Panicf("delete peer should never fail: %v", err) log.Panicf("delete peer should never fail: %v", err)
} }

View File

@ -21,6 +21,7 @@ import (
"reflect" "reflect"
"testing" "testing"
"github.com/coreos/etcd/pkg/types"
"github.com/coreos/etcd/store" "github.com/coreos/etcd/store"
) )
@ -115,7 +116,7 @@ func TestClusterMember(t *testing.T) {
newTestMember(2, nil, "node2", nil), newTestMember(2, nil, "node2", nil),
} }
tests := []struct { tests := []struct {
id uint64 id types.ID
match bool match bool
}{ }{
{1, true}, {1, true},
@ -165,7 +166,7 @@ func TestClusterMemberIDs(t *testing.T) {
newTestMember(4, nil, "", nil), newTestMember(4, nil, "", nil),
newTestMember(100, nil, "", nil), newTestMember(100, nil, "", nil),
}) })
w := []uint64{1, 4, 100} w := []types.ID{1, 4, 100}
g := c.MemberIDs() g := c.MemberIDs()
if !reflect.DeepEqual(w, g) { if !reflect.DeepEqual(w, g) {
t.Errorf("IDs = %+v, want %+v", g, w) t.Errorf("IDs = %+v, want %+v", g, w)
@ -327,7 +328,7 @@ func TestClusterValidateAndAssignIDs(t *testing.T) {
tests := []struct { tests := []struct {
clmembs []Member clmembs []Member
membs []*Member membs []*Member
wids []uint64 wids []types.ID
}{ }{
{ {
[]Member{ []Member{
@ -338,7 +339,7 @@ func TestClusterValidateAndAssignIDs(t *testing.T) {
newTestMemberp(3, []string{"http://127.0.0.1:2379"}, "", nil), newTestMemberp(3, []string{"http://127.0.0.1:2379"}, "", nil),
newTestMemberp(4, []string{"http://127.0.0.2:2379"}, "", nil), newTestMemberp(4, []string{"http://127.0.0.2:2379"}, "", nil),
}, },
[]uint64{3, 4}, []types.ID{3, 4},
}, },
} }
for i, tt := range tests { for i, tt := range tests {
@ -439,7 +440,7 @@ func TestClusterAddMember(t *testing.T) {
func TestClusterMembers(t *testing.T) { func TestClusterMembers(t *testing.T) {
cls := &Cluster{ cls := &Cluster{
members: map[uint64]*Member{ members: map[types.ID]*Member{
1: &Member{ID: 1}, 1: &Member{ID: 1},
20: &Member{ID: 20}, 20: &Member{ID: 20},
100: &Member{ID: 100}, 100: &Member{ID: 100},
@ -461,7 +462,7 @@ func TestClusterMembers(t *testing.T) {
func TestClusterString(t *testing.T) { func TestClusterString(t *testing.T) {
cls := &Cluster{ cls := &Cluster{
members: map[uint64]*Member{ members: map[types.ID]*Member{
1: newTestMemberp( 1: newTestMemberp(
1, 1,
[]string{"http://1.1.1.1:1111", "http://0.0.0.0:0000"}, []string{"http://1.1.1.1:1111", "http://0.0.0.0:0000"},
@ -533,7 +534,7 @@ func TestNodeToMember(t *testing.T) {
} }
func newTestCluster(membs []Member) *Cluster { func newTestCluster(membs []Member) *Cluster {
c := &Cluster{members: make(map[uint64]*Member), removed: make(map[uint64]bool)} c := &Cluster{members: make(map[types.ID]*Member), removed: make(map[types.ID]bool)}
for i, m := range membs { for i, m := range membs {
c.members[m.ID] = &membs[i] c.members[m.ID] = &membs[i]
} }
@ -542,7 +543,7 @@ func newTestCluster(membs []Member) *Cluster {
func newTestMember(id uint64, peerURLs []string, name string, clientURLs []string) Member { func newTestMember(id uint64, peerURLs []string, name string, clientURLs []string) Member {
return Member{ return Member{
ID: id, ID: types.ID(id),
RaftAttributes: RaftAttributes{PeerURLs: peerURLs}, RaftAttributes: RaftAttributes{PeerURLs: peerURLs},
Attributes: Attributes{Name: name, ClientURLs: clientURLs}, Attributes: Attributes{Name: name, ClientURLs: clientURLs},
} }

View File

@ -45,7 +45,7 @@ func (c *ServerConfig) VerifyBootstrapConfig() error {
if m == nil { if m == nil {
return fmt.Errorf("couldn't find local name %s in the initial cluster configuration", c.Name) return fmt.Errorf("couldn't find local name %s in the initial cluster configuration", c.Name)
} }
if m.ID == raft.None { if uint64(m.ID) == raft.None {
return fmt.Errorf("cannot use %x as member id", raft.None) return fmt.Errorf("cannot use %x as member id", raft.None)
} }

View File

@ -35,7 +35,7 @@ import (
"github.com/coreos/etcd/etcdserver" "github.com/coreos/etcd/etcdserver"
"github.com/coreos/etcd/etcdserver/etcdhttp/httptypes" "github.com/coreos/etcd/etcdserver/etcdhttp/httptypes"
"github.com/coreos/etcd/etcdserver/etcdserverpb" "github.com/coreos/etcd/etcdserver/etcdserverpb"
"github.com/coreos/etcd/pkg/strutil" "github.com/coreos/etcd/pkg/types"
"github.com/coreos/etcd/store" "github.com/coreos/etcd/store"
"github.com/coreos/etcd/version" "github.com/coreos/etcd/version"
) )
@ -96,8 +96,7 @@ func (h *keysHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if !allowMethod(w, r.Method, "GET", "PUT", "POST", "DELETE") { if !allowMethod(w, r.Method, "GET", "PUT", "POST", "DELETE") {
return return
} }
cid := strconv.FormatUint(h.clusterInfo.ID(), 16) w.Header().Set("X-Etcd-Cluster-ID", h.clusterInfo.ID().String())
w.Header().Set("X-Etcd-Cluster-ID", cid)
ctx, cancel := context.WithTimeout(context.Background(), h.timeout) ctx, cancel := context.WithTimeout(context.Background(), h.timeout)
defer cancel() defer cancel()
@ -152,8 +151,7 @@ func (h *membersHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if !allowMethod(w, r.Method, "GET", "POST", "DELETE") { if !allowMethod(w, r.Method, "GET", "POST", "DELETE") {
return return
} }
cid := strconv.FormatUint(h.clusterInfo.ID(), 16) w.Header().Set("X-Etcd-Cluster-ID", h.clusterInfo.ID().String())
w.Header().Set("X-Etcd-Cluster-ID", cid)
ctx, cancel := context.WithTimeout(context.Background(), defaultServerTimeout) ctx, cancel := context.WithTimeout(context.Background(), defaultServerTimeout)
defer cancel() defer cancel()
@ -189,7 +187,7 @@ func (h *membersHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
now := h.clock.Now() now := h.clock.Now()
m := etcdserver.NewMember("", req.PeerURLs, "", &now) m := etcdserver.NewMember("", req.PeerURLs, "", &now)
if err := h.server.AddMember(ctx, *m); err != nil { if err := h.server.AddMember(ctx, *m); err != nil {
log.Printf("etcdhttp: error adding node %x: %v", m.ID, err) log.Printf("etcdhttp: error adding node %s: %v", m.ID, err)
writeError(w, err) writeError(w, err)
return return
} }
@ -206,17 +204,17 @@ func (h *membersHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed) http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
return return
} }
id, err := strconv.ParseUint(idStr, 16, 64) id, err := types.IDFromString(idStr)
if err != nil { if err != nil {
writeError(w, httptypes.NewHTTPError(http.StatusBadRequest, err.Error())) writeError(w, httptypes.NewHTTPError(http.StatusBadRequest, err.Error()))
return return
} }
err = h.server.RemoveMember(ctx, id) err = h.server.RemoveMember(ctx, uint64(id))
switch { switch {
case err == etcdserver.ErrIDNotFound: case err == etcdserver.ErrIDNotFound:
writeError(w, httptypes.NewHTTPError(http.StatusNotFound, fmt.Sprintf("No such member: %s", idStr))) writeError(w, httptypes.NewHTTPError(http.StatusNotFound, fmt.Sprintf("No such member: %s", idStr)))
case err != nil: case err != nil:
log.Printf("etcdhttp: error removing node %x: %v", id, err) log.Printf("etcdhttp: error removing node %s: %v", id, err)
writeError(w, err) writeError(w, err)
default: default:
w.WriteHeader(http.StatusNoContent) w.WriteHeader(http.StatusNoContent)
@ -544,7 +542,7 @@ func newMemberCollection(ms []*etcdserver.Member) *httptypes.MemberCollection {
func newMember(m *etcdserver.Member) httptypes.Member { func newMember(m *etcdserver.Member) httptypes.Member {
tm := httptypes.Member{ tm := httptypes.Member{
ID: strutil.IDAsHex(m.ID), ID: m.ID.String(),
Name: m.Name, Name: m.Name,
PeerURLs: make([]string, len(m.PeerURLs)), PeerURLs: make([]string, len(m.PeerURLs)),
ClientURLs: make([]string, len(m.ClientURLs)), ClientURLs: make([]string, len(m.ClientURLs)),

View File

@ -27,7 +27,6 @@ import (
"net/url" "net/url"
"path" "path"
"reflect" "reflect"
"strconv"
"strings" "strings"
"testing" "testing"
"time" "time"
@ -38,6 +37,7 @@ import (
"github.com/coreos/etcd/etcdserver" "github.com/coreos/etcd/etcdserver"
"github.com/coreos/etcd/etcdserver/etcdhttp/httptypes" "github.com/coreos/etcd/etcdserver/etcdhttp/httptypes"
"github.com/coreos/etcd/etcdserver/etcdserverpb" "github.com/coreos/etcd/etcdserver/etcdserverpb"
"github.com/coreos/etcd/pkg/types"
"github.com/coreos/etcd/raft/raftpb" "github.com/coreos/etcd/raft/raftpb"
"github.com/coreos/etcd/store" "github.com/coreos/etcd/store"
"github.com/coreos/etcd/version" "github.com/coreos/etcd/version"
@ -591,7 +591,7 @@ func TestServeMembers(t *testing.T) {
t.Errorf("#%d: content-type = %s, want %s", i, gct, tt.wct) t.Errorf("#%d: content-type = %s, want %s", i, gct, tt.wct)
} }
gcid := rw.Header().Get("X-Etcd-Cluster-ID") gcid := rw.Header().Get("X-Etcd-Cluster-ID")
wcid := strconv.FormatUint(cluster.ID(), 16) wcid := cluster.ID().String()
if gcid != wcid { if gcid != wcid {
t.Errorf("#%d: cid = %s, want %s", i, gcid, wcid) t.Errorf("#%d: cid = %s, want %s", i, gcid, wcid)
} }
@ -629,7 +629,7 @@ func TestServeMembersCreate(t *testing.T) {
t.Errorf("content-type = %s, want %s", gct, wct) t.Errorf("content-type = %s, want %s", gct, wct)
} }
gcid := rw.Header().Get("X-Etcd-Cluster-ID") gcid := rw.Header().Get("X-Etcd-Cluster-ID")
wcid := strconv.FormatUint(h.clusterInfo.ID(), 16) wcid := h.clusterInfo.ID().String()
if gcid != wcid { if gcid != wcid {
t.Errorf("cid = %s, want %s", gcid, wcid) t.Errorf("cid = %s, want %s", gcid, wcid)
} }
@ -672,7 +672,7 @@ func TestServeMembersDelete(t *testing.T) {
t.Errorf("code=%d, want %d", rw.Code, wcode) t.Errorf("code=%d, want %d", rw.Code, wcode)
} }
gcid := rw.Header().Get("X-Etcd-Cluster-ID") gcid := rw.Header().Get("X-Etcd-Cluster-ID")
wcid := strconv.FormatUint(h.clusterInfo.ID(), 16) wcid := h.clusterInfo.ID().String()
if gcid != wcid { if gcid != wcid {
t.Errorf("cid = %s, want %s", gcid, wcid) t.Errorf("cid = %s, want %s", gcid, wcid)
} }
@ -819,7 +819,7 @@ func TestServeMembersFail(t *testing.T) {
} }
if rw.Code != http.StatusMethodNotAllowed { if rw.Code != http.StatusMethodNotAllowed {
gcid := rw.Header().Get("X-Etcd-Cluster-ID") gcid := rw.Header().Get("X-Etcd-Cluster-ID")
wcid := strconv.FormatUint(h.clusterInfo.ID(), 16) wcid := h.clusterInfo.ID().String()
if gcid != wcid { if gcid != wcid {
t.Errorf("#%d: cid = %s, want %s", i, gcid, wcid) t.Errorf("#%d: cid = %s, want %s", i, gcid, wcid)
} }
@ -950,7 +950,7 @@ type dummyStats struct {
func (ds *dummyStats) SelfStats() []byte { return ds.data } func (ds *dummyStats) SelfStats() []byte { return ds.data }
func (ds *dummyStats) LeaderStats() []byte { return ds.data } func (ds *dummyStats) LeaderStats() []byte { return ds.data }
func (ds *dummyStats) StoreStats() []byte { return ds.data } func (ds *dummyStats) StoreStats() []byte { return ds.data }
func (ds *dummyStats) UpdateRecvApp(_ uint64, _ int64) {} func (ds *dummyStats) UpdateRecvApp(_ types.ID, _ int64) {}
func TestServeSelfStats(t *testing.T) { func TestServeSelfStats(t *testing.T) {
wb := []byte("some statistics") wb := []byte("some statistics")
@ -1160,7 +1160,7 @@ func TestBadServeKeys(t *testing.T) {
} }
if rw.Code != http.StatusMethodNotAllowed { if rw.Code != http.StatusMethodNotAllowed {
gcid := rw.Header().Get("X-Etcd-Cluster-ID") gcid := rw.Header().Get("X-Etcd-Cluster-ID")
wcid := strconv.FormatUint(h.clusterInfo.ID(), 16) wcid := h.clusterInfo.ID().String()
if gcid != wcid { if gcid != wcid {
t.Errorf("#%d: cid = %s, want %s", i, gcid, wcid) t.Errorf("#%d: cid = %s, want %s", i, gcid, wcid)
} }
@ -1204,7 +1204,7 @@ func TestServeKeysEvent(t *testing.T) {
t.Errorf("got code=%d, want %d", rw.Code, wcode) t.Errorf("got code=%d, want %d", rw.Code, wcode)
} }
gcid := rw.Header().Get("X-Etcd-Cluster-ID") gcid := rw.Header().Get("X-Etcd-Cluster-ID")
wcid := strconv.FormatUint(h.clusterInfo.ID(), 16) wcid := h.clusterInfo.ID().String()
if gcid != wcid { if gcid != wcid {
t.Errorf("cid = %s, want %s", gcid, wcid) t.Errorf("cid = %s, want %s", gcid, wcid)
} }
@ -1254,7 +1254,7 @@ func TestServeKeysWatch(t *testing.T) {
t.Errorf("got code=%d, want %d", rw.Code, wcode) t.Errorf("got code=%d, want %d", rw.Code, wcode)
} }
gcid := rw.Header().Get("X-Etcd-Cluster-ID") gcid := rw.Header().Get("X-Etcd-Cluster-ID")
wcid := strconv.FormatUint(h.clusterInfo.ID(), 16) wcid := h.clusterInfo.ID().String()
if gcid != wcid { if gcid != wcid {
t.Errorf("cid = %s, want %s", gcid, wcid) t.Errorf("cid = %s, want %s", gcid, wcid)
} }

View File

@ -28,6 +28,7 @@ import (
etcdErr "github.com/coreos/etcd/error" etcdErr "github.com/coreos/etcd/error"
"github.com/coreos/etcd/etcdserver" "github.com/coreos/etcd/etcdserver"
"github.com/coreos/etcd/etcdserver/etcdserverpb" "github.com/coreos/etcd/etcdserver/etcdserverpb"
"github.com/coreos/etcd/pkg/types"
"github.com/coreos/etcd/raft/raftpb" "github.com/coreos/etcd/raft/raftpb"
) )
@ -45,7 +46,7 @@ type fakeCluster struct {
members map[uint64]*etcdserver.Member members map[uint64]*etcdserver.Member
} }
func (c *fakeCluster) ID() uint64 { return c.id } func (c *fakeCluster) ID() types.ID { return types.ID(c.id) }
func (c *fakeCluster) ClientURLs() []string { return c.clientURLs } func (c *fakeCluster) ClientURLs() []string { return c.clientURLs }
func (c *fakeCluster) Members() []*etcdserver.Member { func (c *fakeCluster) Members() []*etcdserver.Member {
var sms etcdserver.SortableMemberSlice var sms etcdserver.SortableMemberSlice
@ -55,7 +56,7 @@ func (c *fakeCluster) Members() []*etcdserver.Member {
sort.Sort(sms) sort.Sort(sms)
return []*etcdserver.Member(sms) return []*etcdserver.Member(sms)
} }
func (c *fakeCluster) Member(id uint64) *etcdserver.Member { return c.members[id] } func (c *fakeCluster) Member(id types.ID) *etcdserver.Member { return c.members[uint64(id)] }
// errServer implements the etcd.Server interface for testing. // errServer implements the etcd.Server interface for testing.
// It returns the given error from any Do/Process/AddMember/RemoveMember calls. // It returns the given error from any Do/Process/AddMember/RemoveMember calls.

View File

@ -21,11 +21,10 @@ import (
"io/ioutil" "io/ioutil"
"log" "log"
"net/http" "net/http"
"strconv"
"github.com/coreos/etcd/Godeps/_workspace/src/code.google.com/p/go.net/context" "github.com/coreos/etcd/Godeps/_workspace/src/code.google.com/p/go.net/context"
"github.com/coreos/etcd/etcdserver" "github.com/coreos/etcd/etcdserver"
"github.com/coreos/etcd/pkg/strutil" "github.com/coreos/etcd/pkg/types"
"github.com/coreos/etcd/raft/raftpb" "github.com/coreos/etcd/raft/raftpb"
) )
@ -64,7 +63,7 @@ func (h *raftHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
return return
} }
wcid := strconv.FormatUint(h.clusterInfo.ID(), 16) wcid := h.clusterInfo.ID().String()
w.Header().Set("X-Etcd-Cluster-ID", wcid) w.Header().Set("X-Etcd-Cluster-ID", wcid)
gcid := r.Header.Get("X-Etcd-Cluster-ID") gcid := r.Header.Get("X-Etcd-Cluster-ID")
@ -89,7 +88,7 @@ func (h *raftHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if err := h.server.Process(context.TODO(), m); err != nil { if err := h.server.Process(context.TODO(), m); err != nil {
switch err { switch err {
case etcdserver.ErrRemoved: case etcdserver.ErrRemoved:
log.Printf("etcdhttp: reject message from removed member %s", strutil.IDAsHex(m.From)) log.Printf("etcdhttp: reject message from removed member %s", types.ID(m.From).String())
http.Error(w, "cannot process message from removed member", http.StatusForbidden) http.Error(w, "cannot process message from removed member", http.StatusForbidden)
default: default:
writeError(w, err) writeError(w, err)
@ -97,7 +96,7 @@ func (h *raftHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
return return
} }
if m.Type == raftpb.MsgApp { if m.Type == raftpb.MsgApp {
h.stats.UpdateRecvApp(m.From, r.ContentLength) h.stats.UpdateRecvApp(types.ID(m.From), r.ContentLength)
} }
w.WriteHeader(http.StatusNoContent) w.WriteHeader(http.StatusNoContent)
} }
@ -110,8 +109,7 @@ func (h *peerMembersHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if !allowMethod(w, r.Method, "GET") { if !allowMethod(w, r.Method, "GET") {
return return
} }
cid := strconv.FormatUint(h.clusterInfo.ID(), 16) w.Header().Set("X-Etcd-Cluster-ID", h.clusterInfo.ID().String())
w.Header().Set("X-Etcd-Cluster-ID", cid)
if r.URL.Path != peerMembersPrefix { if r.URL.Path != peerMembersPrefix {
http.Error(w, "bad path", http.StatusBadRequest) http.Error(w, "bad path", http.StatusBadRequest)

View File

@ -24,7 +24,6 @@ import (
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"path" "path"
"strconv"
"strings" "strings"
"testing" "testing"
@ -247,7 +246,7 @@ func TestServeMembersGet(t *testing.T) {
t.Errorf("#%d: body = %s, want %s", i, rw.Body.String(), tt.wbody) t.Errorf("#%d: body = %s, want %s", i, rw.Body.String(), tt.wbody)
} }
gcid := rw.Header().Get("X-Etcd-Cluster-ID") gcid := rw.Header().Get("X-Etcd-Cluster-ID")
wcid := strconv.FormatUint(cluster.ID(), 16) wcid := cluster.ID().String()
if gcid != wcid { if gcid != wcid {
t.Errorf("#%d: cid = %s, want %s", i, gcid, wcid) t.Errorf("#%d: cid = %s, want %s", i, gcid, wcid)
} }

View File

@ -26,7 +26,6 @@ import (
"sort" "sort"
"time" "time"
"github.com/coreos/etcd/pkg/strutil"
"github.com/coreos/etcd/pkg/types" "github.com/coreos/etcd/pkg/types"
) )
@ -43,7 +42,7 @@ type Attributes struct {
} }
type Member struct { type Member struct {
ID uint64 `json:"id"` ID types.ID `json:"id"`
RaftAttributes RaftAttributes
Attributes Attributes
} }
@ -68,7 +67,7 @@ func NewMember(name string, peerURLs types.URLs, clusterName string, now *time.T
} }
hash := sha1.Sum(b) hash := sha1.Sum(b)
m.ID = binary.BigEndian.Uint64(hash[:8]) m.ID = types.ID(binary.BigEndian.Uint64(hash[:8]))
return m return m
} }
@ -81,20 +80,20 @@ func (m *Member) PickPeerURL() string {
return m.PeerURLs[rand.Intn(len(m.PeerURLs))] return m.PeerURLs[rand.Intn(len(m.PeerURLs))]
} }
func memberStoreKey(id uint64) string { func memberStoreKey(id types.ID) string {
return path.Join(storeMembersPrefix, strutil.IDAsHex(id)) return path.Join(storeMembersPrefix, id.String())
} }
func mustParseMemberIDFromKey(key string) uint64 { func mustParseMemberIDFromKey(key string) types.ID {
id, err := strutil.IDFromHex(path.Base(key)) id, err := types.IDFromString(path.Base(key))
if err != nil { if err != nil {
log.Panicf("unexpected parse member id error: %v", err) log.Panicf("unexpected parse member id error: %v", err)
} }
return id return id
} }
func removedMemberStoreKey(id uint64) string { func removedMemberStoreKey(id types.ID) string {
return path.Join(storeRemovedMembersPrefix, strutil.IDAsHex(id)) return path.Join(storeRemovedMembersPrefix, id.String())
} }
type SortableMemberSliceByPeerURLs []*Member type SortableMemberSliceByPeerURLs []*Member

View File

@ -20,6 +20,8 @@ import (
"net/url" "net/url"
"testing" "testing"
"time" "time"
"github.com/coreos/etcd/pkg/types"
) )
func timeParse(value string) *time.Time { func timeParse(value string) *time.Time {
@ -33,7 +35,7 @@ func timeParse(value string) *time.Time {
func TestMemberTime(t *testing.T) { func TestMemberTime(t *testing.T) {
tests := []struct { tests := []struct {
mem *Member mem *Member
id uint64 id types.ID
}{ }{
{NewMember("mem1", []url.URL{{Scheme: "http", Host: "10.0.0.8:2379"}}, "", nil), 14544069596553697298}, {NewMember("mem1", []url.URL{{Scheme: "http", Host: "10.0.0.8:2379"}}, "", nil), 14544069596553697298},
// Same ID, different name (names shouldn't matter) // Same ID, different name (names shouldn't matter)

View File

@ -21,11 +21,10 @@ import (
"fmt" "fmt"
"log" "log"
"net/http" "net/http"
"strconv"
"time" "time"
"github.com/coreos/etcd/etcdserver/stats" "github.com/coreos/etcd/etcdserver/stats"
"github.com/coreos/etcd/pkg/strutil" "github.com/coreos/etcd/pkg/types"
"github.com/coreos/etcd/raft/raftpb" "github.com/coreos/etcd/raft/raftpb"
) )
@ -50,16 +49,17 @@ func Sender(t *http.Transport, cl *Cluster, ss *stats.ServerStats, ls *stats.Lea
// ClusterStore, retrying up to 3 times for each message. The given // ClusterStore, retrying up to 3 times for each message. The given
// ServerStats and LeaderStats are updated appropriately // ServerStats and LeaderStats are updated appropriately
func send(c *http.Client, cl *Cluster, m raftpb.Message, ss *stats.ServerStats, ls *stats.LeaderStats) { func send(c *http.Client, cl *Cluster, m raftpb.Message, ss *stats.ServerStats, ls *stats.LeaderStats) {
to := types.ID(m.To)
cid := cl.ID() cid := cl.ID()
// TODO (xiangli): reasonable retry logic // TODO (xiangli): reasonable retry logic
for i := 0; i < 3; i++ { for i := 0; i < 3; i++ {
memb := cl.Member(m.To) memb := cl.Member(to)
if memb == nil { if memb == nil {
if !cl.IsIDRemoved(m.To) { if !cl.IsIDRemoved(to) {
// TODO: unknown peer id.. what do we do? I // TODO: unknown peer id.. what do we do? I
// don't think his should ever happen, need to // don't think his should ever happen, need to
// look into this further. // look into this further.
log.Printf("etcdserver: error sending message to unknown receiver %s", strutil.IDAsHex(m.To)) log.Printf("etcdserver: error sending message to unknown receiver %s", to.String())
} }
return return
} }
@ -75,8 +75,7 @@ func send(c *http.Client, cl *Cluster, m raftpb.Message, ss *stats.ServerStats,
if m.Type == raftpb.MsgApp { if m.Type == raftpb.MsgApp {
ss.SendAppendReq(len(data)) ss.SendAppendReq(len(data))
} }
to := strutil.IDAsHex(m.To) fs := ls.Follower(to.String())
fs := ls.Follower(to)
start := time.Now() start := time.Now()
sent := httpPost(c, u, cid, data) sent := httpPost(c, u, cid, data)
@ -92,14 +91,14 @@ func send(c *http.Client, cl *Cluster, m raftpb.Message, ss *stats.ServerStats,
// httpPost POSTs a data payload to a url using the given client. Returns true // httpPost POSTs a data payload to a url using the given client. Returns true
// if the POST succeeds, false on any failure. // if the POST succeeds, false on any failure.
func httpPost(c *http.Client, url string, cid uint64, data []byte) bool { func httpPost(c *http.Client, url string, cid types.ID, data []byte) bool {
req, err := http.NewRequest("POST", url, bytes.NewBuffer(data)) req, err := http.NewRequest("POST", url, bytes.NewBuffer(data))
if err != nil { if err != nil {
// TODO: log the error? // TODO: log the error?
return false return false
} }
req.Header.Set("Content-Type", "application/protobuf") req.Header.Set("Content-Type", "application/protobuf")
req.Header.Set("X-Etcd-Cluster-ID", strconv.FormatUint(cid, 16)) req.Header.Set("X-Etcd-Cluster-ID", cid.String())
resp, err := c.Do(req) resp, err := c.Do(req)
if err != nil { if err != nil {
// TODO: log the error? // TODO: log the error?
@ -110,7 +109,7 @@ func httpPost(c *http.Client, url string, cid uint64, data []byte) bool {
switch resp.StatusCode { switch resp.StatusCode {
case http.StatusPreconditionFailed: case http.StatusPreconditionFailed:
// TODO: shutdown the etcdserver gracefully? // TODO: shutdown the etcdserver gracefully?
log.Fatalf("etcd: conflicting cluster ID with the target cluster (%s != %s). Exiting.", resp.Header.Get("X-Etcd-Cluster-ID"), strutil.IDAsHex(cid)) log.Fatalf("etcd: conflicting cluster ID with the target cluster (%s != %s). Exiting.", resp.Header.Get("X-Etcd-Cluster-ID"), cid.String())
return false return false
case http.StatusForbidden: case http.StatusForbidden:
// TODO: stop the server // TODO: stop the server

View File

@ -27,7 +27,6 @@ import (
"os" "os"
"path" "path"
"regexp" "regexp"
"strconv"
"sync/atomic" "sync/atomic"
"time" "time"
@ -36,7 +35,7 @@ import (
pb "github.com/coreos/etcd/etcdserver/etcdserverpb" pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
"github.com/coreos/etcd/etcdserver/stats" "github.com/coreos/etcd/etcdserver/stats"
"github.com/coreos/etcd/pkg/pbutil" "github.com/coreos/etcd/pkg/pbutil"
"github.com/coreos/etcd/pkg/strutil" "github.com/coreos/etcd/pkg/types"
"github.com/coreos/etcd/raft" "github.com/coreos/etcd/raft"
"github.com/coreos/etcd/raft/raftpb" "github.com/coreos/etcd/raft/raftpb"
"github.com/coreos/etcd/snap" "github.com/coreos/etcd/snap"
@ -132,7 +131,7 @@ type Stats interface {
// StoreStats returns statistics of the store backing this EtcdServer // StoreStats returns statistics of the store backing this EtcdServer
StoreStats() []byte StoreStats() []byte
// UpdateRecvApp updates the underlying statistics in response to a receiving an Append request // UpdateRecvApp updates the underlying statistics in response to a receiving an Append request
UpdateRecvApp(from uint64, length int64) UpdateRecvApp(from types.ID, length int64)
} }
type RaftTimer interface { type RaftTimer interface {
@ -145,7 +144,7 @@ type EtcdServer struct {
w wait.Wait w wait.Wait
done chan struct{} done chan struct{}
stopped chan struct{} stopped chan struct{}
id uint64 id types.ID
attributes Attributes attributes Attributes
Cluster *Cluster Cluster *Cluster
@ -184,7 +183,7 @@ func NewServer(cfg *ServerConfig) *EtcdServer {
st := store.New() st := store.New()
var w *wal.WAL var w *wal.WAL
var n raft.Node var n raft.Node
var id uint64 var id types.ID
haveWAL := wal.Exist(cfg.WALDir()) haveWAL := wal.Exist(cfg.WALDir())
switch { switch {
case !haveWAL && cfg.ClusterState == ClusterStateValueExisting: case !haveWAL && cfg.ClusterState == ClusterStateValueExisting:
@ -240,9 +239,9 @@ func NewServer(cfg *ServerConfig) *EtcdServer {
sstats := &stats.ServerStats{ sstats := &stats.ServerStats{
Name: cfg.Name, Name: cfg.Name,
ID: strutil.IDAsHex(id), ID: id.String(),
} }
lstats := stats.NewLeaderStats(strutil.IDAsHex(id)) lstats := stats.NewLeaderStats(id.String())
s := &EtcdServer{ s := &EtcdServer{
store: st, store: st,
@ -290,7 +289,7 @@ func (s *EtcdServer) start() {
} }
func (s *EtcdServer) Process(ctx context.Context, m raftpb.Message) error { func (s *EtcdServer) Process(ctx context.Context, m raftpb.Message) error {
if s.Cluster.IsIDRemoved(m.From) { if s.Cluster.IsIDRemoved(types.ID(m.From)) {
return ErrRemoved return ErrRemoved
} }
return s.node.Step(ctx, m) return s.node.Step(ctx, m)
@ -423,8 +422,8 @@ func (s *EtcdServer) StoreStats() []byte {
return s.store.JsonStats() return s.store.JsonStats()
} }
func (s *EtcdServer) UpdateRecvApp(from uint64, length int64) { func (s *EtcdServer) UpdateRecvApp(from types.ID, length int64) {
s.stats.RecvAppendReq(strutil.IDAsHex(from), int(length)) s.stats.RecvAppendReq(from.String(), int(length))
} }
func (s *EtcdServer) AddMember(ctx context.Context, memb Member) error { func (s *EtcdServer) AddMember(ctx context.Context, memb Member) error {
@ -436,7 +435,7 @@ func (s *EtcdServer) AddMember(ctx context.Context, memb Member) error {
cc := raftpb.ConfChange{ cc := raftpb.ConfChange{
ID: GenID(), ID: GenID(),
Type: raftpb.ConfChangeAddNode, Type: raftpb.ConfChangeAddNode,
NodeID: memb.ID, NodeID: uint64(memb.ID),
Context: b, Context: b,
} }
return s.configure(ctx, cc) return s.configure(ctx, cc)
@ -528,7 +527,7 @@ func (s *EtcdServer) publish(retryInterval time.Duration) {
cancel() cancel()
switch err { switch err {
case nil: case nil:
log.Printf("etcdserver: published %+v to cluster %x", s.attributes, s.Cluster.ID()) log.Printf("etcdserver: published %+v to cluster %s", s.attributes, s.Cluster.ID())
return return
case ErrStopped: case ErrStopped:
log.Printf("etcdserver: aborting publish because server is stopped") log.Printf("etcdserver: aborting publish because server is stopped")
@ -595,7 +594,7 @@ func (s *EtcdServer) applyRequest(r pb.Request) Response {
id := mustParseMemberIDFromKey(path.Dir(r.Path)) id := mustParseMemberIDFromKey(path.Dir(r.Path))
m := s.Cluster.Member(id) m := s.Cluster.Member(id)
if m == nil { if m == nil {
log.Fatalf("fetch member %x should never fail", id) log.Fatalf("fetch member %s should never fail", id)
} }
if err := json.Unmarshal([]byte(r.Val), &m.Attributes); err != nil { if err := json.Unmarshal([]byte(r.Val), &m.Attributes); err != nil {
log.Fatalf("unmarshal %s should never fail: %v", r.Val, err) log.Fatalf("unmarshal %s should never fail: %v", r.Val, err)
@ -634,18 +633,18 @@ func (s *EtcdServer) applyConfChange(cc raftpb.ConfChange, nodes []uint64) error
if err := json.Unmarshal(cc.Context, m); err != nil { if err := json.Unmarshal(cc.Context, m); err != nil {
panic("unexpected unmarshal error") panic("unexpected unmarshal error")
} }
if cc.NodeID != m.ID { if cc.NodeID != uint64(m.ID) {
panic("unexpected nodeID mismatch") panic("unexpected nodeID mismatch")
} }
s.Cluster.AddMember(m) s.Cluster.AddMember(m)
case raftpb.ConfChangeRemoveNode: case raftpb.ConfChangeRemoveNode:
s.Cluster.RemoveMember(cc.NodeID) s.Cluster.RemoveMember(types.ID(cc.NodeID))
} }
return nil return nil
} }
func (s *EtcdServer) checkConfChange(cc raftpb.ConfChange, nodes []uint64) error { func (s *EtcdServer) checkConfChange(cc raftpb.ConfChange, nodes []uint64) error {
if s.Cluster.IsIDRemoved(cc.NodeID) { if s.Cluster.IsIDRemoved(types.ID(cc.NodeID)) {
return ErrIDRemoved return ErrIDRemoved
} }
switch cc.Type { switch cc.Type {
@ -692,7 +691,7 @@ func GetClusterFromPeers(urls []string) (*Cluster, error) {
log.Printf("etcdserver: unmarshal body error: %v", err) log.Printf("etcdserver: unmarshal body error: %v", err)
continue continue
} }
id, err := strconv.ParseUint(resp.Header.Get("X-Etcd-Cluster-ID"), 16, 64) id, err := types.IDFromString(resp.Header.Get("X-Etcd-Cluster-ID"))
if err != nil { if err != nil {
log.Printf("etcdserver: parse uint error: %v", err) log.Printf("etcdserver: parse uint error: %v", err)
continue continue
@ -702,12 +701,17 @@ func GetClusterFromPeers(urls []string) (*Cluster, error) {
return nil, fmt.Errorf("etcdserver: could not retrieve cluster information from the given urls") return nil, fmt.Errorf("etcdserver: could not retrieve cluster information from the given urls")
} }
func startNode(cfg *ServerConfig, ids []uint64) (id uint64, n raft.Node, w *wal.WAL) { func startNode(cfg *ServerConfig, ids []types.ID) (id types.ID, n raft.Node, w *wal.WAL) {
var err error var err error
// TODO: remove the discoveryURL when it becomes part of the source for // TODO: remove the discoveryURL when it becomes part of the source for
// generating nodeID. // generating nodeID.
member := cfg.Cluster.MemberByName(cfg.Name) member := cfg.Cluster.MemberByName(cfg.Name)
metadata := pbutil.MustMarshal(&pb.Metadata{NodeID: member.ID, ClusterID: cfg.Cluster.ID()}) metadata := pbutil.MustMarshal(
&pb.Metadata{
NodeID: uint64(member.ID),
ClusterID: uint64(cfg.Cluster.ID()),
},
)
if w, err = wal.Create(cfg.WALDir(), metadata); err != nil { if w, err = wal.Create(cfg.WALDir(), metadata); err != nil {
log.Fatal(err) log.Fatal(err)
} }
@ -717,15 +721,15 @@ func startNode(cfg *ServerConfig, ids []uint64) (id uint64, n raft.Node, w *wal.
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
peers[i] = raft.Peer{ID: id, Context: ctx} peers[i] = raft.Peer{ID: uint64(id), Context: ctx}
} }
id = member.ID id = member.ID
log.Printf("etcdserver: start node %x in cluster %x", id, cfg.Cluster.ID()) log.Printf("etcdserver: start node %x in cluster %x", id.String(), cfg.Cluster.ID().String())
n = raft.StartNode(id, peers, 10, 1) n = raft.StartNode(uint64(id), peers, 10, 1)
return return
} }
func restartNode(cfg *ServerConfig, index uint64, snapshot *raftpb.Snapshot) (id uint64, n raft.Node, w *wal.WAL) { func restartNode(cfg *ServerConfig, index uint64, snapshot *raftpb.Snapshot) (id types.ID, n raft.Node, w *wal.WAL) {
var err error var err error
// restart a node from previous wal // restart a node from previous wal
if w, err = wal.OpenAtIndex(cfg.WALDir(), index); err != nil { if w, err = wal.OpenAtIndex(cfg.WALDir(), index); err != nil {
@ -738,10 +742,10 @@ func restartNode(cfg *ServerConfig, index uint64, snapshot *raftpb.Snapshot) (id
var metadata pb.Metadata var metadata pb.Metadata
pbutil.MustUnmarshal(&metadata, wmetadata) pbutil.MustUnmarshal(&metadata, wmetadata)
id = metadata.NodeID id = types.ID(metadata.NodeID)
cfg.Cluster.SetID(metadata.ClusterID) cfg.Cluster.SetID(types.ID(metadata.ClusterID))
log.Printf("etcdserver: restart member %x in cluster %x at commit index %d", id, cfg.Cluster.ID(), st.Commit) log.Printf("etcdserver: restart member %s in cluster %s at commit index %d", id, cfg.Cluster.ID(), st.Commit)
n = raft.RestartNode(id, 10, 1, snapshot, st, ents) n = raft.RestartNode(uint64(id), 10, 1, snapshot, st, ents)
return return
} }

View File

@ -30,6 +30,7 @@ import (
"github.com/coreos/etcd/Godeps/_workspace/src/code.google.com/p/go.net/context" "github.com/coreos/etcd/Godeps/_workspace/src/code.google.com/p/go.net/context"
pb "github.com/coreos/etcd/etcdserver/etcdserverpb" pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
"github.com/coreos/etcd/pkg/testutil" "github.com/coreos/etcd/pkg/testutil"
"github.com/coreos/etcd/pkg/types"
"github.com/coreos/etcd/raft" "github.com/coreos/etcd/raft"
"github.com/coreos/etcd/raft/raftpb" "github.com/coreos/etcd/raft/raftpb"
"github.com/coreos/etcd/store" "github.com/coreos/etcd/store"
@ -407,7 +408,7 @@ func TestApplyRequestOnAdminMemberAttributes(t *testing.T) {
// TODO: test ErrIDRemoved // TODO: test ErrIDRemoved
func TestApplyConfChangeError(t *testing.T) { func TestApplyConfChangeError(t *testing.T) {
nodes := []uint64{1, 2, 3} nodes := []uint64{1, 2, 3}
removed := map[uint64]bool{4: true} removed := map[types.ID]bool{4: true}
tests := []struct { tests := []struct {
cc raftpb.ConfChange cc raftpb.ConfChange
werr error werr error
@ -1381,7 +1382,7 @@ func (cs *removedClusterStore) IsRemoved(id uint64) bool { return cs.removed[id]
func mustMakePeerSlice(t *testing.T, ids ...uint64) []raft.Peer { func mustMakePeerSlice(t *testing.T, ids ...uint64) []raft.Peer {
peers := make([]raft.Peer, len(ids)) peers := make([]raft.Peer, len(ids))
for i, id := range ids { for i, id := range ids {
m := Member{ID: id} m := Member{ID: types.ID(id)}
b, err := json.Marshal(m) b, err := json.Marshal(m)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)

View File

@ -1,29 +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 strutil
import (
"strconv"
)
func IDAsHex(id uint64) string {
return strconv.FormatUint(id, 16)
}
func IDFromHex(s string) (uint64, error) {
return strconv.ParseUint(s, 16, 64)
}

43
pkg/types/id.go Normal file
View File

@ -0,0 +1,43 @@
/*
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 types
import (
"strconv"
)
// ID represents a generic identifier which is canonically
// stored as a uint64 but is typically represented as a
// base-16 string for input/output
type ID uint64
func (i ID) String() string {
return strconv.FormatUint(uint64(i), 16)
}
// IDFromString attempts to create an ID from a base-16 string.
func IDFromString(s string) (ID, error) {
i, err := strconv.ParseUint(s, 16, 64)
return ID(i), err
}
// IDSlice implements the sort interface
type IDSlice []ID
func (p IDSlice) Len() int { return len(p) }
func (p IDSlice) Less(i, j int) bool { return uint64(p[i]) < uint64(p[j]) }
func (p IDSlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] }

View File

@ -13,64 +13,65 @@
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package types
package strutil
import ( import (
"reflect"
"sort"
"testing" "testing"
) )
func TestIDAsHex(t *testing.T) { func TestIDString(t *testing.T) {
tests := []struct { tests := []struct {
input uint64 input ID
want string want string
}{ }{
{ {
input: uint64(12), input: 12,
want: "c", want: "c",
}, },
{ {
input: uint64(4918257920282737594), input: 4918257920282737594,
want: "444129853c343bba", want: "444129853c343bba",
}, },
} }
for i, tt := range tests { for i, tt := range tests {
got := IDAsHex(tt.input) got := tt.input.String()
if tt.want != got { if tt.want != got {
t.Errorf("#%d: IDAsHex failure: want=%v, got=%v", i, tt.want, got) t.Errorf("#%d: ID.String failure: want=%v, got=%v", i, tt.want, got)
} }
} }
} }
func TestIDFromHex(t *testing.T) { func TestIDFromString(t *testing.T) {
tests := []struct { tests := []struct {
input string input string
want uint64 want ID
}{ }{
{ {
input: "17", input: "17",
want: uint64(23), want: 23,
}, },
{ {
input: "612840dae127353", input: "612840dae127353",
want: uint64(437557308098245459), want: 437557308098245459,
}, },
} }
for i, tt := range tests { for i, tt := range tests {
got, err := IDFromHex(tt.input) got, err := IDFromString(tt.input)
if err != nil { if err != nil {
t.Errorf("#%d: IDFromHex failure: err=%v", i, err) t.Errorf("#%d: IDFromString failure: err=%v", i, err)
continue continue
} }
if tt.want != got { if tt.want != got {
t.Errorf("#%d: IDFromHex failure: want=%v, got=%v", i, tt.want, got) t.Errorf("#%d: IDFromString failure: want=%v, got=%v", i, tt.want, got)
} }
} }
} }
func TestIDFromHexFail(t *testing.T) { func TestIDFromStringFail(t *testing.T) {
tests := []string{ tests := []string{
"", "",
"XXX", "XXX",
@ -78,9 +79,18 @@ func TestIDFromHexFail(t *testing.T) {
} }
for i, tt := range tests { for i, tt := range tests {
_, err := IDFromHex(tt) _, err := IDFromString(tt)
if err == nil { if err == nil {
t.Fatalf("#%d: IDFromHex expected error, but err=nil", i) t.Fatalf("#%d: IDFromString expected error, but err=nil", i)
} }
} }
} }
func TestIDSlice(t *testing.T) {
g := []ID{10, 500, 5, 1, 100, 25}
w := []ID{1, 5, 10, 25, 100, 500}
sort.Sort(IDSlice(g))
if !reflect.DeepEqual(g, w) {
t.Errorf("slice after sort = %#v, want %#v", g, w)
}
}

2
test
View File

@ -15,7 +15,7 @@ COVER=${COVER:-"-cover"}
source ./build source ./build
# Hack: gofmt ./ will recursively check the .git directory. So use *.go for gofmt. # Hack: gofmt ./ will recursively check the .git directory. So use *.go for gofmt.
TESTABLE_AND_FORMATTABLE="client discovery etcdctl/command etcdmain etcdserver etcdserver/etcdhttp etcdserver/etcdhttp/httptypes etcdserver/etcdserverpb integration pkg/flags pkg/strutil pkg/transport proxy raft snap store wait wal" TESTABLE_AND_FORMATTABLE="client discovery etcdctl/command etcdmain etcdserver etcdserver/etcdhttp etcdserver/etcdhttp/httptypes etcdserver/etcdserverpb integration pkg/flags pkg/types pkg/transport proxy raft snap store wait wal"
FORMATTABLE="$TESTABLE_AND_FORMATTABLE *.go etcdctl/" FORMATTABLE="$TESTABLE_AND_FORMATTABLE *.go etcdctl/"
# user has not provided PKG override # user has not provided PKG override