feat(command): add version to join command

Add a version to the join command. Add a versioning document to discuss
some of the design decisions.
release-0.4
Brandon Philips 2013-08-18 17:31:38 -07:00
parent e091923311
commit 53b2038d2e
5 changed files with 80 additions and 3 deletions

View File

@ -0,0 +1,61 @@
# Versioning
Goal: We want to be able to upgrade an individual machine in an etcd cluster to a newer version of etcd.
The process will take the form of individual followers upgrading to the latest version until the entire cluster is on the new version.
Immediate need: etcd is moving too fast to version the internal API right now.
But, we need to keep mixed version clusters from being started by a rollowing upgrade process (e.g. the CoreOS developer alpha).
Longer term need: Having a mixed version cluster where all machines are not be running the exact same version of etcd itself but are able to speak one version of the internal protocol.
Solution: The internal protocol needs to be versioned just as the client protocol is.
Initially during the 0.\*.\* series of etcd releases we won't allow mixed versions at all.
## Join Control
We will add a version field to the join command.
But, who decides whether a newly upgraded follower should be able to join a cluster?
### Leader Controlled
If the leader controls the version of followers joining the cluster then it compares its version to the version number presented by the follower in the JoinCommand and rejects the join if the number is less than the leader's version number.
Advantages
- Leader controls all cluster decisions still
Disadvantages
- Follower knows better what versions of the interal protocol it can talk than the leader
### Follower Controlled
A newly upgraded follower should be able to figure out the leaders internal version from a defined internal backwards compatible API endpoint and figure out if it can join the cluster.
If it cannot join the cluster then it simply exits.
Advantages
- The follower is running newer code and knows better if it can talk older protocols
Disadvantages
- This cluster decision isn't made by the leader
## Recommendation
To solve the immediate need and to plan for the future lets do the following:
- Add Version field to JoinCommand
- Have a joining follower read the Version field of the leader and if its own version doesn't match the leader then sleep for some random interval and retry later to see if the leader has upgraded.
# Research
## Zookeeper versioning
Zookeeper very recently added versioning into the protocol and it doesn't seem to have seen any use yet.
https://issues.apache.org/jira/browse/ZOOKEEPER-1633
## doozerd
doozerd stores the version number of the machine in the datastore for other clients to check, no decisions are made off of this number currently.

View File

@ -117,6 +117,7 @@ func (c *WatchCommand) Apply(server *raft.Server) (interface{}, error) {
// JoinCommand
type JoinCommand struct {
Version string `json:"version"`
Name string `json:"name"`
RaftURL string `json:"raftURL"`
EtcdURL string `json:"etcdURL"`
@ -124,6 +125,9 @@ type JoinCommand struct {
func newJoinCommand() *JoinCommand {
return &JoinCommand{
// TODO: This will be the internal protocol version but tie it
// to the release tag for now.
Version: r.version,
Name: r.name,
RaftURL: r.url,
EtcdURL: e.url,
@ -152,14 +156,14 @@ func (c *JoinCommand) Apply(raftServer *raft.Server) (interface{}, error) {
return []byte("join fail"), etcdErr.NewError(103, "")
}
addNameToURL(c.Name, c.RaftURL, c.EtcdURL)
addNameToURL(c.Name, c.Version, c.RaftURL, c.EtcdURL)
// add peer in raft
err := raftServer.AddPeer(c.Name, "")
// add machine in etcd storage
key := path.Join("_etcd/machines", c.Name)
value := fmt.Sprintf("raft=%s&etcd=%s", c.RaftURL, c.EtcdURL)
value := fmt.Sprintf("raft=%s&etcd=%s&version=%s", c.RaftURL, c.EtcdURL, c.Version)
etcdStore.Set(key, value, time.Unix(0, 0), raftServer.CommitIndex())
return []byte("join success"), err

View File

@ -7,6 +7,7 @@ import (
// we map node name to url
type nodeInfo struct {
version string
raftURL string
etcdURL string
}
@ -39,8 +40,9 @@ func nameToRaftURL(name string) (string, bool) {
}
// addNameToURL add a name that maps to raftURL and etcdURL
func addNameToURL(name string, raftURL string, etcdURL string) {
func addNameToURL(name string, version string, raftURL string, etcdURL string) {
namesMap[name] = &nodeInfo{
version: version,
raftURL: raftURL,
etcdURL: etcdURL,
}

View File

@ -113,3 +113,10 @@ func NameHttpHandler(w http.ResponseWriter, req *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte(r.name))
}
// Response to the name request
func RaftVersionHttpHandler(w http.ResponseWriter, req *http.Request) {
debugf("[recv] Get %s/version/ ", r.url)
w.WriteHeader(http.StatusOK)
w.Write([]byte(r.version))
}

View File

@ -14,6 +14,7 @@ import (
type raftServer struct {
*raft.Server
version string
name string
url string
tlsConf *TLSConfig
@ -34,6 +35,7 @@ func newRaftServer(name string, url string, tlsConf *TLSConfig, tlsInfo *TLSInfo
return &raftServer{
Server: server,
version: releaseVersion,
name: name,
url: url,
tlsConf: tlsConf,
@ -144,6 +146,7 @@ func (r *raftServer) startTransport(scheme string, tlsConf tls.Config) {
// internal commands
raftMux.HandleFunc("/name", NameHttpHandler)
raftMux.HandleFunc("/version", RaftVersionHttpHandler)
raftMux.Handle("/join", errorHandler(JoinHttpHandler))
raftMux.HandleFunc("/vote", VoteHttpHandler)
raftMux.HandleFunc("/log", GetLogHttpHandler)