diff --git a/etcdserver/cluster.go b/etcdserver/cluster.go index 3779e84a3..d2d11ddb0 100644 --- a/etcdserver/cluster.go +++ b/etcdserver/cluster.go @@ -53,6 +53,8 @@ type Cluster interface { // IsIDRemoved checks whether the given ID has been removed from this // cluster at some point in the past IsIDRemoved(id types.ID) bool + // ClusterVersion is the cluster-wide minimum major.minor version. + Version() *semver.Version } // Cluster is a list of Members that belong to the same raft cluster diff --git a/etcdserver/etcdhttp/client.go b/etcdserver/etcdhttp/client.go index 299a1757e..4f185dbd2 100644 --- a/etcdserver/etcdhttp/client.go +++ b/etcdserver/etcdhttp/client.go @@ -92,7 +92,7 @@ func NewClientHandler(server *etcdserver.EtcdServer) http.Handler { mux := http.NewServeMux() mux.HandleFunc("/", http.NotFound) mux.Handle(healthPath, healthHandler(server)) - mux.HandleFunc(versionPath, serveVersion) + mux.HandleFunc(versionPath, versionHandler(server.Cluster(), serveVersion)) mux.Handle(keysPrefix, kh) mux.Handle(keysPrefix+"/", kh) mux.HandleFunc(statsPrefix+"/store", sh.serveStore) @@ -357,11 +357,31 @@ func healthHandler(server *etcdserver.EtcdServer) http.HandlerFunc { } } -func serveVersion(w http.ResponseWriter, r *http.Request) { +func versionHandler(c etcdserver.Cluster, fn func(http.ResponseWriter, *http.Request, string)) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + v := c.Version() + if v != nil { + fn(w, r, v.String()) + } else { + fn(w, r, "not_decided") + } + } +} + +func serveVersion(w http.ResponseWriter, r *http.Request, clusterV string) { if !allowMethod(w, r.Method, "GET") { return } - w.Write(version.MarshalJSON()) + vs := version.Versions{ + Server: version.Version, + Cluster: clusterV, + } + + b, err := json.Marshal(&vs) + if err != nil { + log.Panicf("version: cannot marshal versions to json (%v)", err) + } + w.Write(b) } // parseKeyRequest converts a received http.Request on keysPrefix to diff --git a/etcdserver/etcdhttp/client_test.go b/etcdserver/etcdhttp/client_test.go index c5779a117..8bed49411 100644 --- a/etcdserver/etcdhttp/client_test.go +++ b/etcdserver/etcdhttp/client_test.go @@ -1326,11 +1326,18 @@ func TestServeVersion(t *testing.T) { t.Fatalf("error creating request: %v", err) } rw := httptest.NewRecorder() - serveVersion(rw, req) + serveVersion(rw, req, "2.1.0") if rw.Code != http.StatusOK { t.Errorf("code=%d, want %d", rw.Code, http.StatusOK) } - w := version.MarshalJSON() + vs := version.Versions{ + Server: version.Version, + Cluster: "2.1.0", + } + w, err := json.Marshal(&vs) + if err != nil { + t.Fatal(err) + } if g := rw.Body.String(); g != string(w) { t.Fatalf("body = %q, want %q", g, string(w)) } @@ -1345,7 +1352,7 @@ func TestServeVersionFails(t *testing.T) { t.Fatalf("error creating request: %v", err) } rw := httptest.NewRecorder() - serveVersion(rw, req) + serveVersion(rw, req, "2.1.0") if rw.Code != http.StatusMethodNotAllowed { t.Errorf("method %s: code=%d, want %d", m, rw.Code, http.StatusMethodNotAllowed) } diff --git a/etcdserver/etcdhttp/http_test.go b/etcdserver/etcdhttp/http_test.go index 9f0933217..9dbc08674 100644 --- a/etcdserver/etcdhttp/http_test.go +++ b/etcdserver/etcdhttp/http_test.go @@ -48,6 +48,7 @@ func (c *fakeCluster) Members() []*etcdserver.Member { } func (c *fakeCluster) Member(id types.ID) *etcdserver.Member { return c.members[uint64(id)] } func (c *fakeCluster) IsIDRemoved(id types.ID) bool { return false } +func (c *fakeCluster) Version() *semver.Version { return nil } // errServer implements the etcd.Server interface for testing. // It returns the given error from any Do/Process/AddMember/RemoveMember calls. diff --git a/etcdserver/etcdhttp/peer.go b/etcdserver/etcdhttp/peer.go index a54f687dc..b7df13d4d 100644 --- a/etcdserver/etcdhttp/peer.go +++ b/etcdserver/etcdhttp/peer.go @@ -38,7 +38,7 @@ func NewPeerHandler(cluster etcdserver.Cluster, raftHandler http.Handler) http.H mux.Handle(rafthttp.RaftPrefix, raftHandler) mux.Handle(rafthttp.RaftPrefix+"/", raftHandler) mux.Handle(peerMembersPrefix, mh) - mux.HandleFunc(versionPath, serveVersion) + mux.HandleFunc(versionPath, versionHandler(cluster, serveVersion)) return mux } diff --git a/version/version.go b/version/version.go index 3c4cf1dd3..ea72fd4a3 100644 --- a/version/version.go +++ b/version/version.go @@ -15,8 +15,6 @@ package version import ( - "encoding/json" - "log" "os" "path" @@ -45,20 +43,11 @@ const ( ) type Versions struct { - Server string `json:"etcdserver"` - // TODO: etcdcluster version + Server string `json:"etcdserver"` + Cluster string `json:"etcdcluster"` // TODO: raft state machine version } -// MarshalJSON returns the JSON encoding of Versions struct. -func MarshalJSON() []byte { - b, err := json.Marshal(Versions{Server: Version}) - if err != nil { - log.Panicf("version: cannot marshal versions to json (%v)", err) - } - return b -} - func DetectDataDir(dirpath string) (DataDirVersion, error) { names, err := fileutil.ReadDir(dirpath) if err != nil {