From e7f8bf7c44d9e4b8ab10c9060aac5e3c473cc7f1 Mon Sep 17 00:00:00 2001 From: ahrtr Date: Thu, 10 Mar 2022 06:55:18 +0800 Subject: [PATCH] enhance the /version endpoint to add storageVersion --- CHANGELOG/CHANGELOG-3.6.md | 1 + api/version/version.go | 1 + server/etcdserver/adapters.go | 13 +--------- server/etcdserver/api/etcdhttp/peer.go | 2 +- server/etcdserver/api/etcdhttp/peer_test.go | 1 + server/etcdserver/api/etcdhttp/version.go | 24 ++++++++++-------- .../etcdserver/api/etcdhttp/version_test.go | 25 +++++++++++-------- server/etcdserver/server.go | 16 ++++++++++++ 8 files changed, 49 insertions(+), 34 deletions(-) diff --git a/CHANGELOG/CHANGELOG-3.6.md b/CHANGELOG/CHANGELOG-3.6.md index 61b154d1f..2bb7f5cb6 100644 --- a/CHANGELOG/CHANGELOG-3.6.md +++ b/CHANGELOG/CHANGELOG-3.6.md @@ -50,6 +50,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.5.0...v3.6.0). - Add [`etcdctl make-mirror --rev`](https://github.com/etcd-io/etcd/pull/13519) flag to support incremental mirror. - Add [`etcd --experimental-wait-cluster-ready-timeout`](https://github.com/etcd-io/etcd/pull/13525) flag to wait for cluster to be ready before serving client requests. - Add [v3 discovery](https://github.com/etcd-io/etcd/pull/13635) to bootstrap a new etcd cluster. +- Add [field `storage`](https://github.com/etcd-io/etcd/pull/13772) into the response body of endpoint `/version`. - Fix [non mutating requests pass through quotaKVServer when NOSPACE](https://github.com/etcd-io/etcd/pull/13435) - Fix [exclude the same alarm type activated by multiple peers](https://github.com/etcd-io/etcd/pull/13467). - Fix [Provide a better liveness probe for when etcd runs as a Kubernetes pod](https://github.com/etcd-io/etcd/pull/13399) diff --git a/api/version/version.go b/api/version/version.go index 07cffa09d..6dce4d8ea 100644 --- a/api/version/version.go +++ b/api/version/version.go @@ -43,6 +43,7 @@ func init() { type Versions struct { Server string `json:"etcdserver"` Cluster string `json:"etcdcluster"` + Storage string `json:"storage"` // TODO: raft state machine version } diff --git a/server/etcdserver/adapters.go b/server/etcdserver/adapters.go index d875cf14e..f864507bf 100644 --- a/server/etcdserver/adapters.go +++ b/server/etcdserver/adapters.go @@ -74,18 +74,7 @@ func (s *serverVersionAdapter) GetMembersVersions() map[string]*version.Versions } func (s *serverVersionAdapter) GetStorageVersion() *semver.Version { - // `applySnapshot` sets a new backend instance, so we need to acquire the bemu lock. - s.bemu.RLock() - defer s.bemu.RUnlock() - - tx := s.be.ReadTx() - tx.RLock() - defer tx.RUnlock() - v, err := schema.UnsafeDetectSchemaVersion(s.lg, tx) - if err != nil { - return nil - } - return &v + return s.StorageVersion() } func (s *serverVersionAdapter) UpdateStorageVersion(target semver.Version) error { diff --git a/server/etcdserver/api/etcdhttp/peer.go b/server/etcdserver/api/etcdhttp/peer.go index 4470bf9e6..a64058b71 100644 --- a/server/etcdserver/api/etcdhttp/peer.go +++ b/server/etcdserver/api/etcdhttp/peer.go @@ -71,7 +71,7 @@ func newPeerHandler( if hashKVHandler != nil { mux.Handle(etcdserver.PeerHashKVPath, hashKVHandler) } - mux.HandleFunc(versionPath, versionHandler(s.Cluster(), serveVersion)) + mux.HandleFunc(versionPath, versionHandler(s, serveVersion)) return mux } diff --git a/server/etcdserver/api/etcdhttp/peer_test.go b/server/etcdserver/api/etcdhttp/peer_test.go index 68f651aef..dae50fb4b 100644 --- a/server/etcdserver/api/etcdhttp/peer_test.go +++ b/server/etcdserver/api/etcdhttp/peer_test.go @@ -74,6 +74,7 @@ func (s *fakeServer) PromoteMember(ctx context.Context, id uint64) ([]*membershi return nil, fmt.Errorf("PromoteMember not implemented in fakeServer") } func (s *fakeServer) ClusterVersion() *semver.Version { return nil } +func (s *fakeServer) StorageVersion() *semver.Version { return nil } func (s *fakeServer) Cluster() api.Cluster { return s.cluster } func (s *fakeServer) Alarms() []*pb.AlarmMember { return s.alarms } func (s *fakeServer) LeaderChangedNotify() <-chan struct{} { return nil } diff --git a/server/etcdserver/api/etcdhttp/version.go b/server/etcdserver/api/etcdhttp/version.go index 296861175..8090703a0 100644 --- a/server/etcdserver/api/etcdhttp/version.go +++ b/server/etcdserver/api/etcdhttp/version.go @@ -21,35 +21,39 @@ import ( "go.etcd.io/etcd/api/v3/version" "go.etcd.io/etcd/server/v3/etcdserver" - "go.etcd.io/etcd/server/v3/etcdserver/api" ) const ( versionPath = "/version" ) -func HandleVersion(mux *http.ServeMux, server etcdserver.ServerPeer) { - mux.HandleFunc(versionPath, versionHandler(server.Cluster(), serveVersion)) +func HandleVersion(mux *http.ServeMux, server etcdserver.Server) { + mux.HandleFunc(versionPath, versionHandler(server, serveVersion)) } -func versionHandler(c api.Cluster, fn func(http.ResponseWriter, *http.Request, string)) http.HandlerFunc { +func versionHandler(server etcdserver.Server, fn func(http.ResponseWriter, *http.Request, string, 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") + clusterVersion := server.ClusterVersion() + storageVersion := server.StorageVersion() + clusterVersionStr, storageVersionStr := "not_decided", "unknown" + if clusterVersion != nil { + clusterVersionStr = clusterVersion.String() } + if storageVersion != nil { + storageVersionStr = storageVersion.String() + } + fn(w, r, clusterVersionStr, storageVersionStr) } } -func serveVersion(w http.ResponseWriter, r *http.Request, clusterV string) { +func serveVersion(w http.ResponseWriter, r *http.Request, clusterV, storageV string) { if !allowMethod(w, r, "GET") { return } vs := version.Versions{ Server: version.Version, Cluster: clusterV, + Storage: storageV, } w.Header().Set("Content-Type", "application/json") diff --git a/server/etcdserver/api/etcdhttp/version_test.go b/server/etcdserver/api/etcdhttp/version_test.go index 37a14dd1d..25e0c4f3c 100644 --- a/server/etcdserver/api/etcdhttp/version_test.go +++ b/server/etcdserver/api/etcdhttp/version_test.go @@ -29,13 +29,14 @@ func TestServeVersion(t *testing.T) { t.Fatalf("error creating request: %v", err) } rw := httptest.NewRecorder() - serveVersion(rw, req, "2.1.0") + serveVersion(rw, req, "3.6.0", "3.5.2") if rw.Code != http.StatusOK { t.Errorf("code=%d, want %d", rw.Code, http.StatusOK) } vs := version.Versions{ Server: version.Version, - Cluster: "2.1.0", + Cluster: "3.6.0", + Storage: "3.5.2", } w, err := json.Marshal(&vs) if err != nil { @@ -53,14 +54,16 @@ func TestServeVersionFails(t *testing.T) { for _, m := range []string{ "CONNECT", "TRACE", "PUT", "POST", "HEAD", } { - req, err := http.NewRequest(m, "", nil) - if err != nil { - t.Fatalf("error creating request: %v", err) - } - rw := httptest.NewRecorder() - serveVersion(rw, req, "2.1.0") - if rw.Code != http.StatusMethodNotAllowed { - t.Errorf("method %s: code=%d, want %d", m, rw.Code, http.StatusMethodNotAllowed) - } + t.Run(m, func(t *testing.T) { + req, err := http.NewRequest(m, "", nil) + if err != nil { + t.Fatalf("error creating request: %v", err) + } + rw := httptest.NewRecorder() + serveVersion(rw, req, "3.6.0", "3.5.2") + if rw.Code != http.StatusMethodNotAllowed { + t.Errorf("method %s: code=%d, want %d", m, rw.Code, http.StatusMethodNotAllowed) + } + }) } } diff --git a/server/etcdserver/server.go b/server/etcdserver/server.go index 3fbef5cbf..545a83cd9 100644 --- a/server/etcdserver/server.go +++ b/server/etcdserver/server.go @@ -186,6 +186,9 @@ type Server interface { // the leader is etcd 2.0. etcd 2.0 leader will not update clusterVersion since // this feature is introduced post 2.0. ClusterVersion() *semver.Version + // StorageVersion is the storage schema version. It's supported starting + // from 3.6. + StorageVersion() *semver.Version Cluster() api.Cluster Alarms() []*pb.AlarmMember @@ -2107,6 +2110,19 @@ func (s *EtcdServer) ClusterVersion() *semver.Version { return s.cluster.Version() } +func (s *EtcdServer) StorageVersion() *semver.Version { + // `applySnapshot` sets a new backend instance, so we need to acquire the bemu lock. + s.bemu.RLock() + defer s.bemu.RUnlock() + + v, err := schema.DetectSchemaVersion(s.lg, s.be.ReadTx()) + if err != nil { + s.lg.Warn("Failed to detect schema version", zap.Error(err)) + return nil + } + return &v +} + // monitorClusterVersions every monitorVersionInterval checks if it's the leader and updates cluster version if needed. func (s *EtcdServer) monitorClusterVersions() { monitor := serverversion.NewMonitor(s.Logger(), NewServerVersionAdapter(s))