Merge pull request #13772 from ahrtr/display_storage_version

Enhance the '/version' endpoint to add storageVersion
This commit is contained in:
Marek Siarkowicz 2022-05-06 21:49:57 +02:00 committed by GitHub
commit e7effc0a08
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 49 additions and 34 deletions

View File

@ -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)

View File

@ -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
}

View File

@ -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 {

View File

@ -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
}

View File

@ -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 }

View File

@ -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")

View File

@ -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)
}
})
}
}

View File

@ -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))