diff --git a/etcdserver/api/membership/cluster.go b/etcdserver/api/membership/cluster.go index 6f4b96a44f1..65ea46edc54 100644 --- a/etcdserver/api/membership/cluster.go +++ b/etcdserver/api/membership/cluster.go @@ -36,6 +36,7 @@ import ( "go.etcd.io/etcd/version" "github.com/coreos/go-semver/semver" + "github.com/prometheus/client_golang/prometheus" "go.uber.org/zap" ) @@ -500,6 +501,7 @@ func (c *RaftCluster) SetVersion(ver *semver.Version, onSet func(*zap.Logger, *s if c.be != nil { mustSaveClusterVersionToBackend(c.be, ver) } + ClusterVersionMetrics.With(prometheus.Labels{"cluster_version": ver.String()}).Set(1) onSet(c.lg, ver) } diff --git a/etcdserver/api/membership/metrics.go b/etcdserver/api/membership/metrics.go new file mode 100644 index 00000000000..b3212bc80cd --- /dev/null +++ b/etcdserver/api/membership/metrics.go @@ -0,0 +1,31 @@ +// Copyright 2018 The etcd Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package membership + +import "github.com/prometheus/client_golang/prometheus" + +var ( + ClusterVersionMetrics = prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: "etcd", + Subsystem: "cluster", + Name: "version", + Help: "Which version is running. 1 for 'cluster_version' label with current cluster version", + }, + []string{"cluster_version"}) +) + +func init() { + prometheus.MustRegister(ClusterVersionMetrics) +} diff --git a/etcdserver/server.go b/etcdserver/server.go index dafb0fad3f6..5a97b8341ef 100644 --- a/etcdserver/server.go +++ b/etcdserver/server.go @@ -768,6 +768,7 @@ func (s *EtcdServer) start() { } else { plog.Infof("starting server... [version: %v, cluster version: %v]", version.Version, version.Cluster(s.ClusterVersion().String())) } + membership.ClusterVersionMetrics.With(prometheus.Labels{"cluster_version": s.ClusterVersion().String()}).Set(1) } else { if lg != nil { lg.Info( diff --git a/tests/e2e/etcd_release_upgrade_test.go b/tests/e2e/etcd_release_upgrade_test.go index bb8cb7853f2..6a8e5b13ac6 100644 --- a/tests/e2e/etcd_release_upgrade_test.go +++ b/tests/e2e/etcd_release_upgrade_test.go @@ -17,6 +17,7 @@ package e2e import ( "fmt" "os" + "strings" "sync" "testing" "time" @@ -101,6 +102,15 @@ func TestReleaseUpgrade(t *testing.T) { } } } + + // expect upgraded cluster version + ver := version.Version + if strings.HasSuffix(ver, "+git") { + ver = strings.Replace(ver, "+git", "", 1) + } + if err := cURLGet(cx.epc, cURLReq{endpoint: "/metrics", expected: fmt.Sprintf(`etcd_cluster_version{cluster_version="%s"} 1`, ver), metricsURLScheme: cx.cfg.metricsURLScheme}); err != nil { + cx.t.Fatalf("failed get with curl (%v)", err) + } } func TestReleaseUpgradeWithRestart(t *testing.T) { diff --git a/tests/e2e/metrics_test.go b/tests/e2e/metrics_test.go index 24ca95e4d63..7a32f003147 100644 --- a/tests/e2e/metrics_test.go +++ b/tests/e2e/metrics_test.go @@ -16,6 +16,7 @@ package e2e import ( "fmt" + "strings" "testing" "go.etcd.io/etcd/version" @@ -45,6 +46,13 @@ func metricsTest(cx ctlCtx) { if err := cURLGet(cx.epc, cURLReq{endpoint: "/metrics", expected: fmt.Sprintf(`etcd_server_version{server_version="%s"} 1`, version.Version), metricsURLScheme: cx.cfg.metricsURLScheme}); err != nil { cx.t.Fatalf("failed get with curl (%v)", err) } + ver := version.Version + if strings.HasSuffix(ver, "+git") { + ver = strings.Replace(ver, "+git", "", 1) + } + if err := cURLGet(cx.epc, cURLReq{endpoint: "/metrics", expected: fmt.Sprintf(`etcd_cluster_version{cluster_version="%s"} 1`, ver), metricsURLScheme: cx.cfg.metricsURLScheme}); err != nil { + cx.t.Fatalf("failed get with curl (%v)", err) + } if err := cURLGet(cx.epc, cURLReq{endpoint: "/health", expected: `{"health":"true"}`, metricsURLScheme: cx.cfg.metricsURLScheme}); err != nil { cx.t.Fatalf("failed get with curl (%v)", err) }