mirror of
https://github.com/etcd-io/etcd.git
synced 2024-09-27 06:25:44 +00:00
Merge pull request #13772 from ahrtr/display_storage_version
Enhance the '/version' endpoint to add storageVersion
This commit is contained in:
commit
e7effc0a08
@ -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)
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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 }
|
||||
|
@ -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")
|
||||
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -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))
|
||||
|
Loading…
x
Reference in New Issue
Block a user