From 617e12791aa0471e262aaf21def1a289cd9d0fd8 Mon Sep 17 00:00:00 2001 From: kekkokers Date: Sun, 10 Dec 2023 16:50:37 +0200 Subject: [PATCH 1/7] feat: add cluster license collector Signed-off-by: kekkokers --- collector/cluster_license.go | 176 ++++++++++++++++++++++++++ collector/cluster_license_response.go | 32 +++++ collector/cluster_license_test.go | 102 +++++++++++++++ fixtures/clusterlicense/basic.json | 14 ++ fixtures/clusterlicense/platinum.json | 15 +++ main.go | 7 + 6 files changed, 346 insertions(+) create mode 100644 collector/cluster_license.go create mode 100644 collector/cluster_license_response.go create mode 100644 collector/cluster_license_test.go create mode 100644 fixtures/clusterlicense/basic.json create mode 100644 fixtures/clusterlicense/platinum.json diff --git a/collector/cluster_license.go b/collector/cluster_license.go new file mode 100644 index 00000000..6a6c653d --- /dev/null +++ b/collector/cluster_license.go @@ -0,0 +1,176 @@ +// Copyright 2023 The Prometheus 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 collector + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "path" + + "github.com/go-kit/log" + "github.com/go-kit/log/level" + "github.com/prometheus/client_golang/prometheus" +) + +type clusterLicenseMetric struct { + Type prometheus.ValueType + Desc *prometheus.Desc + Value func(clusterLicenseStats clusterLicenseResponse) float64 + Labels func(clusterLicenseStats clusterLicenseResponse) []string +} + +var ( + defaultClusterLicenseLabels = []string{"cluster_license_type"} + defaultClusterLicenseValues = func(clusterLicense clusterLicenseResponse) []string { + return []string{clusterLicense.License.Type} + } +) + +// License Information Struct +type ClusterLicense struct { + logger log.Logger + client *http.Client + url *url.URL + + clusterLicenseMetrics []*clusterLicenseMetric +} + +// NewClusterLicense defines ClusterLicense Prometheus metrics +func NewClusterLicense(logger log.Logger, client *http.Client, url *url.URL) *ClusterLicense { + return &ClusterLicense{ + logger: logger, + client: client, + url: url, + + clusterLicenseMetrics: []*clusterLicenseMetric{ + { + Type: prometheus.GaugeValue, + Desc: prometheus.NewDesc( + prometheus.BuildFQName(namespace, "cluster_license", "max_nodes"), + "The max amount of nodes allowed by the license", + defaultClusterLicenseLabels, nil, + ), + Value: func(clusterLicenseStats clusterLicenseResponse) float64 { + return float64(clusterLicenseStats.License.MaxNodes) + }, + Labels: defaultClusterLicenseValues, + }, + { + Type: prometheus.GaugeValue, + Desc: prometheus.NewDesc( + prometheus.BuildFQName(namespace, "cluster_license", "issue_date_in_millis"), + "License issue date in milliseconds", + defaultClusterLicenseLabels, nil, + ), + Value: func(clusterLicenseStats clusterLicenseResponse) float64 { + return float64(clusterLicenseStats.License.IssueDateInMillis) + }, + Labels: defaultClusterLicenseValues, + }, + { + Type: prometheus.GaugeValue, + Desc: prometheus.NewDesc( + prometheus.BuildFQName(namespace, "cluster_license", "expiry_date_in_millis"), + "License expiry date in milliseconds", + defaultClusterLicenseLabels, nil, + ), + Value: func(clusterLicenseStats clusterLicenseResponse) float64 { + return float64(clusterLicenseStats.License.ExpiryDateInMillis) + }, + Labels: defaultClusterLicenseValues, + }, + { + Type: prometheus.GaugeValue, + Desc: prometheus.NewDesc( + prometheus.BuildFQName(namespace, "cluster_license", "start_date_in_millis"), + "License start date in milliseconds", + defaultClusterLicenseLabels, nil, + ), + Value: func(clusterLicenseStats clusterLicenseResponse) float64 { + return float64(clusterLicenseStats.License.StartDateInMillis) + }, + Labels: defaultClusterLicenseValues, + }, + }, + } +} + +// Describe adds License metrics descriptions +func (cl *ClusterLicense) Describe(ch chan<- *prometheus.Desc) { + for _, metric := range cl.clusterLicenseMetrics { + ch <- metric.Desc + } +} + +func (cl *ClusterLicense) fetchAndDecodeClusterLicense() (clusterLicenseResponse, error) { + var clr clusterLicenseResponse + + u := *cl.url + u.Path = path.Join(u.Path, "/_license") + res, err := cl.client.Get(u.String()) + if err != nil { + return clr, fmt.Errorf("failed to get license stats from %s://%s:%s%s: %s", + u.Scheme, u.Hostname(), u.Port(), u.Path, err) + } + + defer func() { + err = res.Body.Close() + if err != nil { + level.Warn(cl.logger).Log( + "msg", "failed to close http.Client", + "err", err, + ) + } + }() + + if res.StatusCode != http.StatusOK { + return clr, fmt.Errorf("HTTP Request failed with code %d", res.StatusCode) + } + + bts, err := io.ReadAll(res.Body) + if err != nil { + return clr, err + } + + if err := json.Unmarshal(bts, &clr); err != nil { + return clr, err + } + + return clr, nil +} + +// Collect gets ClusterLicense metric values +func (cl *ClusterLicense) Collect(ch chan<- prometheus.Metric) { + + clusterLicenseResp, err := cl.fetchAndDecodeClusterLicense() + if err != nil { + level.Warn(cl.logger).Log( + "msg", "failed to fetch and decode license stats", + "err", err, + ) + return + } + + for _, metric := range cl.clusterLicenseMetrics { + ch <- prometheus.MustNewConstMetric( + metric.Desc, + metric.Type, + metric.Value(clusterLicenseResp), + metric.Labels(clusterLicenseResp)..., + ) + } +} diff --git a/collector/cluster_license_response.go b/collector/cluster_license_response.go new file mode 100644 index 00000000..8bef6792 --- /dev/null +++ b/collector/cluster_license_response.go @@ -0,0 +1,32 @@ +// Copyright 2023 The Prometheus 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 collector + +import "time" + +type clusterLicenseResponse struct { + License struct { + Status string `json:"status"` + UID string `json:"uid"` + Type string `json:"type"` + IssueDate time.Time `json:"issue_date"` + IssueDateInMillis int64 `json:"issue_date_in_millis"` + ExpiryDate time.Time `json:"expiry_date"` + ExpiryDateInMillis int64 `json:"expiry_date_in_millis"` + MaxNodes int `json:"max_nodes"` + IssuedTo string `json:"issued_to"` + Issuer string `json:"issuer"` + StartDateInMillis int64 `json:"start_date_in_millis"` + } `json:"license"` +} diff --git a/collector/cluster_license_test.go b/collector/cluster_license_test.go new file mode 100644 index 00000000..8af53bf2 --- /dev/null +++ b/collector/cluster_license_test.go @@ -0,0 +1,102 @@ +// Copyright 2023 The Prometheus 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 collector + +import ( + "io" + "net/http" + "net/http/httptest" + "net/url" + "os" + "strings" + "testing" + + "github.com/go-kit/log" + "github.com/prometheus/client_golang/prometheus/testutil" +) + +func TestIndicesHealth(t *testing.T) { + // Testcases created using: + // docker run -d -p 9200:9200 elasticsearch:VERSION + // curl http://localhost:9200/_license + tests := []struct { + name string + file string + want string + }{ + { + name: "basic", + file: "../fixtures/clusterlicense/basic.json", + want: ` + # HELP elasticsearch_cluster_license_expiry_date_in_millis License expiry date in milliseconds + # TYPE elasticsearch_cluster_license_expiry_date_in_millis gauge + elasticsearch_cluster_license_expiry_date_in_millis{cluster_license_type="basic"} 0 + # HELP elasticsearch_cluster_license_issue_date_in_millis License issue date in milliseconds + # TYPE elasticsearch_cluster_license_issue_date_in_millis gauge + elasticsearch_cluster_license_issue_date_in_millis{cluster_license_type="basic"} 1.702196247064e+12 + # HELP elasticsearch_cluster_license_max_nodes The max amount of nodes allowed by the license + # TYPE elasticsearch_cluster_license_max_nodes gauge + elasticsearch_cluster_license_max_nodes{cluster_license_type="basic"} 1000 + # HELP elasticsearch_cluster_license_start_date_in_millis License start date in milliseconds + # TYPE elasticsearch_cluster_license_start_date_in_millis gauge + elasticsearch_cluster_license_start_date_in_millis{cluster_license_type="basic"} -1 + `, + }, + { + name: "platinum", + file: "../fixtures/clusterlicense/platinum.json", + want: ` + # HELP elasticsearch_cluster_license_expiry_date_in_millis License expiry date in milliseconds + # TYPE elasticsearch_cluster_license_expiry_date_in_millis gauge + elasticsearch_cluster_license_expiry_date_in_millis{cluster_license_type="platinum"} 1.714521599999e+12 + # HELP elasticsearch_cluster_license_issue_date_in_millis License issue date in milliseconds + # TYPE elasticsearch_cluster_license_issue_date_in_millis gauge + elasticsearch_cluster_license_issue_date_in_millis{cluster_license_type="platinum"} 1.6192224e+12 + # HELP elasticsearch_cluster_license_max_nodes The max amount of nodes allowed by the license + # TYPE elasticsearch_cluster_license_max_nodes gauge + elasticsearch_cluster_license_max_nodes{cluster_license_type="platinum"} 10 + # HELP elasticsearch_cluster_license_start_date_in_millis License start date in milliseconds + # TYPE elasticsearch_cluster_license_start_date_in_millis gauge + elasticsearch_cluster_license_start_date_in_millis{cluster_license_type="platinum"} 1.6192224e+12 + `, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + f, err := os.Open(tt.file) + if err != nil { + t.Fatal(err) + } + defer f.Close() + + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + io.Copy(w, f) + })) + + defer ts.Close() + + u, err := url.Parse(ts.URL) + if err != nil { + t.Fatal(err) + } + + c := NewClusterLicense(log.NewNopLogger(), http.DefaultClient, u) + + if err := testutil.CollectAndCompare(c, strings.NewReader(tt.want)); err != nil { + t.Fatal(err) + } + }) + } +} diff --git a/fixtures/clusterlicense/basic.json b/fixtures/clusterlicense/basic.json new file mode 100644 index 00000000..67cdf833 --- /dev/null +++ b/fixtures/clusterlicense/basic.json @@ -0,0 +1,14 @@ +{ + "license": { + "status": "active", + "uid": "redacted", + "type": "basic", + "issue_date": "2023-12-10T08:17:27.064Z", + "issue_date_in_millis": 1702196247064, + "max_nodes": 1000, + "max_resource_units": null, + "issued_to": "redacted", + "issuer": "elasticsearch", + "start_date_in_millis": -1 + } +} diff --git a/fixtures/clusterlicense/platinum.json b/fixtures/clusterlicense/platinum.json new file mode 100644 index 00000000..b9b06009 --- /dev/null +++ b/fixtures/clusterlicense/platinum.json @@ -0,0 +1,15 @@ +{ + "license": { + "status": "active", + "uid": "redacted", + "type": "platinum", + "issue_date": "2021-04-24T00:00:00.000Z", + "issue_date_in_millis": 1619222400000, + "expiry_date": "2024-04-30T23:59:59.999Z", + "expiry_date_in_millis": 1714521599999, + "max_nodes": 10, + "issued_to": "redacted", + "issuer": "API", + "start_date_in_millis": 1619222400000 + } +} diff --git a/main.go b/main.go index c7db1def..5eee49f8 100644 --- a/main.go +++ b/main.go @@ -92,6 +92,9 @@ func main() { esClusterInfoInterval = kingpin.Flag("es.clusterinfo.interval", "Cluster info update interval for the cluster label"). Default("5m").Duration() + esExportLicense = kingpin.Flag("es.license", + "Export license information"). + Default("false").Bool() esCA = kingpin.Flag("es.ca", "Path to PEM file that contains trusted Certificate Authorities for the Elasticsearch connection."). Default("").String() @@ -229,6 +232,10 @@ func main() { prometheus.MustRegister(collector.NewIlmIndicies(logger, httpClient, esURL)) } + if *esExportLicense { + prometheus.MustRegister(collector.NewClusterLicense(logger, httpClient, esURL)) + } + // Create a context that is cancelled on SIGKILL or SIGINT. ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, os.Kill) defer cancel() From 0c7408550411b928366ff82138242635c19816cf Mon Sep 17 00:00:00 2001 From: kekkokers Date: Tue, 12 Dec 2023 19:16:40 +0200 Subject: [PATCH 2/7] Remove cluster_license_response file Signed-off-by: kekkokers --- collector/cluster_license.go | 17 ++++++++++++++ collector/cluster_license_response.go | 32 --------------------------- 2 files changed, 17 insertions(+), 32 deletions(-) delete mode 100644 collector/cluster_license_response.go diff --git a/collector/cluster_license.go b/collector/cluster_license.go index 6a6c653d..4dfb0df9 100644 --- a/collector/cluster_license.go +++ b/collector/cluster_license.go @@ -20,6 +20,7 @@ import ( "net/http" "net/url" "path" + "time" "github.com/go-kit/log" "github.com/go-kit/log/level" @@ -33,6 +34,22 @@ type clusterLicenseMetric struct { Labels func(clusterLicenseStats clusterLicenseResponse) []string } +type clusterLicenseResponse struct { + License struct { + Status string `json:"status"` + UID string `json:"uid"` + Type string `json:"type"` + IssueDate time.Time `json:"issue_date"` + IssueDateInMillis int64 `json:"issue_date_in_millis"` + ExpiryDate time.Time `json:"expiry_date"` + ExpiryDateInMillis int64 `json:"expiry_date_in_millis"` + MaxNodes int `json:"max_nodes"` + IssuedTo string `json:"issued_to"` + Issuer string `json:"issuer"` + StartDateInMillis int64 `json:"start_date_in_millis"` + } `json:"license"` +} + var ( defaultClusterLicenseLabels = []string{"cluster_license_type"} defaultClusterLicenseValues = func(clusterLicense clusterLicenseResponse) []string { diff --git a/collector/cluster_license_response.go b/collector/cluster_license_response.go deleted file mode 100644 index 8bef6792..00000000 --- a/collector/cluster_license_response.go +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright 2023 The Prometheus 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 collector - -import "time" - -type clusterLicenseResponse struct { - License struct { - Status string `json:"status"` - UID string `json:"uid"` - Type string `json:"type"` - IssueDate time.Time `json:"issue_date"` - IssueDateInMillis int64 `json:"issue_date_in_millis"` - ExpiryDate time.Time `json:"expiry_date"` - ExpiryDateInMillis int64 `json:"expiry_date_in_millis"` - MaxNodes int `json:"max_nodes"` - IssuedTo string `json:"issued_to"` - Issuer string `json:"issuer"` - StartDateInMillis int64 `json:"start_date_in_millis"` - } `json:"license"` -} From 728e44730d2105d68776a58e7626d45088d14974 Mon Sep 17 00:00:00 2001 From: kekkokers Date: Tue, 12 Dec 2023 19:29:11 +0200 Subject: [PATCH 3/7] Remove esExportLicense flag Signed-off-by: kekkokers --- main.go | 7 ------- 1 file changed, 7 deletions(-) diff --git a/main.go b/main.go index 5eee49f8..c7db1def 100644 --- a/main.go +++ b/main.go @@ -92,9 +92,6 @@ func main() { esClusterInfoInterval = kingpin.Flag("es.clusterinfo.interval", "Cluster info update interval for the cluster label"). Default("5m").Duration() - esExportLicense = kingpin.Flag("es.license", - "Export license information"). - Default("false").Bool() esCA = kingpin.Flag("es.ca", "Path to PEM file that contains trusted Certificate Authorities for the Elasticsearch connection."). Default("").String() @@ -232,10 +229,6 @@ func main() { prometheus.MustRegister(collector.NewIlmIndicies(logger, httpClient, esURL)) } - if *esExportLicense { - prometheus.MustRegister(collector.NewClusterLicense(logger, httpClient, esURL)) - } - // Create a context that is cancelled on SIGKILL or SIGINT. ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, os.Kill) defer cancel() From b4149e12606a5c9d16445f897fe5992f5798f574 Mon Sep 17 00:00:00 2001 From: kekkokers Date: Tue, 12 Dec 2023 20:39:43 +0200 Subject: [PATCH 4/7] Refactor cluster_license, use the collector interface Signed-off-by: kekkokers --- collector/cluster_license.go | 184 ++++++++++++------------------ collector/cluster_license_test.go | 26 +++-- 2 files changed, 90 insertions(+), 120 deletions(-) diff --git a/collector/cluster_license.go b/collector/cluster_license.go index 4dfb0df9..c6b0372a 100644 --- a/collector/cluster_license.go +++ b/collector/cluster_license.go @@ -14,6 +14,7 @@ package collector import ( + "context" "encoding/json" "fmt" "io" @@ -27,13 +28,6 @@ import ( "github.com/prometheus/client_golang/prometheus" ) -type clusterLicenseMetric struct { - Type prometheus.ValueType - Desc *prometheus.Desc - Value func(clusterLicenseStats clusterLicenseResponse) float64 - Labels func(clusterLicenseStats clusterLicenseResponse) []string -} - type clusterLicenseResponse struct { License struct { Status string `json:"status"` @@ -51,103 +45,69 @@ type clusterLicenseResponse struct { } var ( - defaultClusterLicenseLabels = []string{"cluster_license_type"} - defaultClusterLicenseValues = func(clusterLicense clusterLicenseResponse) []string { - return []string{clusterLicense.License.Type} + defaultClusterLicenseLabels = []string{"issued_to", "issuer", "type", "status"} + defaultClusterLicenseLabelsValues = func(clusterLicense clusterLicenseResponse) []string { + return []string{clusterLicense.License.IssuedTo, clusterLicense.License.Issuer, clusterLicense.License.Type, clusterLicense.License.Status} } ) +var ( + licenseMaxNodes = prometheus.NewDesc( + prometheus.BuildFQName(namespace, "cluster_license", "max_nodes"), + "The max amount of nodes allowed by the license", + defaultClusterLicenseLabels, nil, + ) + licenseIssueDate = prometheus.NewDesc( + prometheus.BuildFQName(namespace, "cluster_license", "issue_date_in_millis"), + "License issue date in milliseconds", + defaultClusterLicenseLabels, nil, + ) + licenseExpiryDate = prometheus.NewDesc( + prometheus.BuildFQName(namespace, "cluster_license", "expiry_date_in_millis"), + "License expiry date in milliseconds", + defaultClusterLicenseLabels, nil, + ) + licenseStartDate = prometheus.NewDesc( + prometheus.BuildFQName(namespace, "cluster_license", "start_date_in_millis"), + "License start date in milliseconds", + defaultClusterLicenseLabels, nil, + ) +) + +func init() { + registerCollector("cluster_license", defaultDisabled, NewClusterLicense) +} + // License Information Struct type ClusterLicense struct { logger log.Logger - client *http.Client - url *url.URL - - clusterLicenseMetrics []*clusterLicenseMetric + hc *http.Client + u *url.URL } -// NewClusterLicense defines ClusterLicense Prometheus metrics -func NewClusterLicense(logger log.Logger, client *http.Client, url *url.URL) *ClusterLicense { +func NewClusterLicense(logger log.Logger, u *url.URL, hc *http.Client) (Collector, error) { return &ClusterLicense{ logger: logger, - client: client, - url: url, - - clusterLicenseMetrics: []*clusterLicenseMetric{ - { - Type: prometheus.GaugeValue, - Desc: prometheus.NewDesc( - prometheus.BuildFQName(namespace, "cluster_license", "max_nodes"), - "The max amount of nodes allowed by the license", - defaultClusterLicenseLabels, nil, - ), - Value: func(clusterLicenseStats clusterLicenseResponse) float64 { - return float64(clusterLicenseStats.License.MaxNodes) - }, - Labels: defaultClusterLicenseValues, - }, - { - Type: prometheus.GaugeValue, - Desc: prometheus.NewDesc( - prometheus.BuildFQName(namespace, "cluster_license", "issue_date_in_millis"), - "License issue date in milliseconds", - defaultClusterLicenseLabels, nil, - ), - Value: func(clusterLicenseStats clusterLicenseResponse) float64 { - return float64(clusterLicenseStats.License.IssueDateInMillis) - }, - Labels: defaultClusterLicenseValues, - }, - { - Type: prometheus.GaugeValue, - Desc: prometheus.NewDesc( - prometheus.BuildFQName(namespace, "cluster_license", "expiry_date_in_millis"), - "License expiry date in milliseconds", - defaultClusterLicenseLabels, nil, - ), - Value: func(clusterLicenseStats clusterLicenseResponse) float64 { - return float64(clusterLicenseStats.License.ExpiryDateInMillis) - }, - Labels: defaultClusterLicenseValues, - }, - { - Type: prometheus.GaugeValue, - Desc: prometheus.NewDesc( - prometheus.BuildFQName(namespace, "cluster_license", "start_date_in_millis"), - "License start date in milliseconds", - defaultClusterLicenseLabels, nil, - ), - Value: func(clusterLicenseStats clusterLicenseResponse) float64 { - return float64(clusterLicenseStats.License.StartDateInMillis) - }, - Labels: defaultClusterLicenseValues, - }, - }, - } -} - -// Describe adds License metrics descriptions -func (cl *ClusterLicense) Describe(ch chan<- *prometheus.Desc) { - for _, metric := range cl.clusterLicenseMetrics { - ch <- metric.Desc - } + u: u, + hc: hc, + }, nil } -func (cl *ClusterLicense) fetchAndDecodeClusterLicense() (clusterLicenseResponse, error) { +func (c *ClusterLicense) Update(ctx context.Context, ch chan<- prometheus.Metric) error { var clr clusterLicenseResponse - u := *cl.url + u := *c.u u.Path = path.Join(u.Path, "/_license") - res, err := cl.client.Get(u.String()) + res, err := c.hc.Get(u.String()) + if err != nil { - return clr, fmt.Errorf("failed to get license stats from %s://%s:%s%s: %s", - u.Scheme, u.Hostname(), u.Port(), u.Path, err) + return err } defer func() { err = res.Body.Close() if err != nil { - level.Warn(cl.logger).Log( + level.Warn(c.logger).Log( "msg", "failed to close http.Client", "err", err, ) @@ -155,39 +115,45 @@ func (cl *ClusterLicense) fetchAndDecodeClusterLicense() (clusterLicenseResponse }() if res.StatusCode != http.StatusOK { - return clr, fmt.Errorf("HTTP Request failed with code %d", res.StatusCode) + return fmt.Errorf("HTTP Request failed with code %d", res.StatusCode) } bts, err := io.ReadAll(res.Body) if err != nil { - return clr, err + return err } if err := json.Unmarshal(bts, &clr); err != nil { - return clr, err - } - - return clr, nil -} - -// Collect gets ClusterLicense metric values -func (cl *ClusterLicense) Collect(ch chan<- prometheus.Metric) { - - clusterLicenseResp, err := cl.fetchAndDecodeClusterLicense() - if err != nil { - level.Warn(cl.logger).Log( - "msg", "failed to fetch and decode license stats", - "err", err, - ) - return + return err } - for _, metric := range cl.clusterLicenseMetrics { - ch <- prometheus.MustNewConstMetric( - metric.Desc, - metric.Type, - metric.Value(clusterLicenseResp), - metric.Labels(clusterLicenseResp)..., - ) - } + ch <- prometheus.MustNewConstMetric( + licenseMaxNodes, + prometheus.GaugeValue, + float64(clr.License.MaxNodes), + defaultClusterLicenseLabelsValues(clr)..., + ) + + ch <- prometheus.MustNewConstMetric( + licenseIssueDate, + prometheus.GaugeValue, + float64(clr.License.IssueDateInMillis), + defaultClusterLicenseLabelsValues(clr)..., + ) + + ch <- prometheus.MustNewConstMetric( + licenseExpiryDate, + prometheus.GaugeValue, + float64(clr.License.ExpiryDateInMillis), + defaultClusterLicenseLabelsValues(clr)..., + ) + + ch <- prometheus.MustNewConstMetric( + licenseStartDate, + prometheus.GaugeValue, + float64(clr.License.StartDateInMillis), + defaultClusterLicenseLabelsValues(clr)..., + ) + + return nil } diff --git a/collector/cluster_license_test.go b/collector/cluster_license_test.go index 8af53bf2..fdbc833b 100644 --- a/collector/cluster_license_test.go +++ b/collector/cluster_license_test.go @@ -26,7 +26,7 @@ import ( "github.com/prometheus/client_golang/prometheus/testutil" ) -func TestIndicesHealth(t *testing.T) { +func TestClusterLicense(t *testing.T) { // Testcases created using: // docker run -d -p 9200:9200 elasticsearch:VERSION // curl http://localhost:9200/_license @@ -41,16 +41,16 @@ func TestIndicesHealth(t *testing.T) { want: ` # HELP elasticsearch_cluster_license_expiry_date_in_millis License expiry date in milliseconds # TYPE elasticsearch_cluster_license_expiry_date_in_millis gauge - elasticsearch_cluster_license_expiry_date_in_millis{cluster_license_type="basic"} 0 + elasticsearch_cluster_license_expiry_date_in_millis{issued_to="redacted",issuer="elasticsearch",status="active",type="basic"} 0 # HELP elasticsearch_cluster_license_issue_date_in_millis License issue date in milliseconds # TYPE elasticsearch_cluster_license_issue_date_in_millis gauge - elasticsearch_cluster_license_issue_date_in_millis{cluster_license_type="basic"} 1.702196247064e+12 + elasticsearch_cluster_license_issue_date_in_millis{issued_to="redacted",issuer="elasticsearch",status="active",type="basic"} 1.702196247064e+12 # HELP elasticsearch_cluster_license_max_nodes The max amount of nodes allowed by the license # TYPE elasticsearch_cluster_license_max_nodes gauge - elasticsearch_cluster_license_max_nodes{cluster_license_type="basic"} 1000 + elasticsearch_cluster_license_max_nodes{issued_to="redacted",issuer="elasticsearch",status="active",type="basic"} 1000 # HELP elasticsearch_cluster_license_start_date_in_millis License start date in milliseconds # TYPE elasticsearch_cluster_license_start_date_in_millis gauge - elasticsearch_cluster_license_start_date_in_millis{cluster_license_type="basic"} -1 + elasticsearch_cluster_license_start_date_in_millis{issued_to="redacted",issuer="elasticsearch",status="active",type="basic"} -1 `, }, { @@ -59,16 +59,16 @@ func TestIndicesHealth(t *testing.T) { want: ` # HELP elasticsearch_cluster_license_expiry_date_in_millis License expiry date in milliseconds # TYPE elasticsearch_cluster_license_expiry_date_in_millis gauge - elasticsearch_cluster_license_expiry_date_in_millis{cluster_license_type="platinum"} 1.714521599999e+12 + elasticsearch_cluster_license_expiry_date_in_millis{issued_to="redacted",issuer="API",status="active",type="platinum"} 1.714521599999e+12 # HELP elasticsearch_cluster_license_issue_date_in_millis License issue date in milliseconds # TYPE elasticsearch_cluster_license_issue_date_in_millis gauge - elasticsearch_cluster_license_issue_date_in_millis{cluster_license_type="platinum"} 1.6192224e+12 + elasticsearch_cluster_license_issue_date_in_millis{issued_to="redacted",issuer="API",status="active",type="platinum"} 1.6192224e+12 # HELP elasticsearch_cluster_license_max_nodes The max amount of nodes allowed by the license # TYPE elasticsearch_cluster_license_max_nodes gauge - elasticsearch_cluster_license_max_nodes{cluster_license_type="platinum"} 10 + elasticsearch_cluster_license_max_nodes{issued_to="redacted",issuer="API",status="active",type="platinum"} 10 # HELP elasticsearch_cluster_license_start_date_in_millis License start date in milliseconds # TYPE elasticsearch_cluster_license_start_date_in_millis gauge - elasticsearch_cluster_license_start_date_in_millis{cluster_license_type="platinum"} 1.6192224e+12 + elasticsearch_cluster_license_start_date_in_millis{issued_to="redacted",issuer="API",status="active",type="platinum"} 1.6192224e+12 `, }, } @@ -92,9 +92,13 @@ func TestIndicesHealth(t *testing.T) { t.Fatal(err) } - c := NewClusterLicense(log.NewNopLogger(), http.DefaultClient, u) + c, err := NewClusterLicense(log.NewNopLogger(), u, http.DefaultClient) - if err := testutil.CollectAndCompare(c, strings.NewReader(tt.want)); err != nil { + if err != nil { + t.Fatal(err) + } + + if err := testutil.CollectAndCompare(wrapCollector{c}, strings.NewReader(tt.want)); err != nil { t.Fatal(err) } }) From e283dccacf9f46b0bdf72e26ff6fcc9f7cfc73ac Mon Sep 17 00:00:00 2001 From: kekkokers Date: Tue, 12 Dec 2023 21:01:30 +0200 Subject: [PATCH 5/7] Convert millis to seconds Signed-off-by: kekkokers --- collector/cluster_license.go | 20 ++++++++-------- collector/cluster_license_test.go | 40 +++++++++++++++---------------- 2 files changed, 30 insertions(+), 30 deletions(-) diff --git a/collector/cluster_license.go b/collector/cluster_license.go index c6b0372a..d156e39a 100644 --- a/collector/cluster_license.go +++ b/collector/cluster_license.go @@ -54,22 +54,22 @@ var ( var ( licenseMaxNodes = prometheus.NewDesc( prometheus.BuildFQName(namespace, "cluster_license", "max_nodes"), - "The max amount of nodes allowed by the license", + "The max amount of nodes allowed by the license.", defaultClusterLicenseLabels, nil, ) licenseIssueDate = prometheus.NewDesc( - prometheus.BuildFQName(namespace, "cluster_license", "issue_date_in_millis"), - "License issue date in milliseconds", + prometheus.BuildFQName(namespace, "cluster_license", "issue_date_seconds"), + "License issue date since unix epoch in seconds.", defaultClusterLicenseLabels, nil, ) licenseExpiryDate = prometheus.NewDesc( - prometheus.BuildFQName(namespace, "cluster_license", "expiry_date_in_millis"), - "License expiry date in milliseconds", + prometheus.BuildFQName(namespace, "cluster_license", "expiry_date_seconds"), + "License expiry date since unix epoch in seconds.", defaultClusterLicenseLabels, nil, ) licenseStartDate = prometheus.NewDesc( - prometheus.BuildFQName(namespace, "cluster_license", "start_date_in_millis"), - "License start date in milliseconds", + prometheus.BuildFQName(namespace, "cluster_license", "start_date_seconds"), + "License start date since unix epoch in seconds.", defaultClusterLicenseLabels, nil, ) ) @@ -137,21 +137,21 @@ func (c *ClusterLicense) Update(ctx context.Context, ch chan<- prometheus.Metric ch <- prometheus.MustNewConstMetric( licenseIssueDate, prometheus.GaugeValue, - float64(clr.License.IssueDateInMillis), + float64(clr.License.IssueDateInMillis/1000), defaultClusterLicenseLabelsValues(clr)..., ) ch <- prometheus.MustNewConstMetric( licenseExpiryDate, prometheus.GaugeValue, - float64(clr.License.ExpiryDateInMillis), + float64(clr.License.ExpiryDateInMillis/1000), defaultClusterLicenseLabelsValues(clr)..., ) ch <- prometheus.MustNewConstMetric( licenseStartDate, prometheus.GaugeValue, - float64(clr.License.StartDateInMillis), + float64(clr.License.StartDateInMillis/1000), defaultClusterLicenseLabelsValues(clr)..., ) diff --git a/collector/cluster_license_test.go b/collector/cluster_license_test.go index fdbc833b..24a880b3 100644 --- a/collector/cluster_license_test.go +++ b/collector/cluster_license_test.go @@ -39,36 +39,36 @@ func TestClusterLicense(t *testing.T) { name: "basic", file: "../fixtures/clusterlicense/basic.json", want: ` - # HELP elasticsearch_cluster_license_expiry_date_in_millis License expiry date in milliseconds - # TYPE elasticsearch_cluster_license_expiry_date_in_millis gauge - elasticsearch_cluster_license_expiry_date_in_millis{issued_to="redacted",issuer="elasticsearch",status="active",type="basic"} 0 - # HELP elasticsearch_cluster_license_issue_date_in_millis License issue date in milliseconds - # TYPE elasticsearch_cluster_license_issue_date_in_millis gauge - elasticsearch_cluster_license_issue_date_in_millis{issued_to="redacted",issuer="elasticsearch",status="active",type="basic"} 1.702196247064e+12 - # HELP elasticsearch_cluster_license_max_nodes The max amount of nodes allowed by the license + # HELP elasticsearch_cluster_license_expiry_date_seconds License expiry date since unix epoch in seconds. + # TYPE elasticsearch_cluster_license_expiry_date_seconds gauge + elasticsearch_cluster_license_expiry_date_seconds{issued_to="redacted",issuer="elasticsearch",status="active",type="basic"} 0 + # HELP elasticsearch_cluster_license_issue_date_seconds License issue date since unix epoch in seconds. + # TYPE elasticsearch_cluster_license_issue_date_seconds gauge + elasticsearch_cluster_license_issue_date_seconds{issued_to="redacted",issuer="elasticsearch",status="active",type="basic"} 1.702196247e+09 + # HELP elasticsearch_cluster_license_max_nodes The max amount of nodes allowed by the license. # TYPE elasticsearch_cluster_license_max_nodes gauge elasticsearch_cluster_license_max_nodes{issued_to="redacted",issuer="elasticsearch",status="active",type="basic"} 1000 - # HELP elasticsearch_cluster_license_start_date_in_millis License start date in milliseconds - # TYPE elasticsearch_cluster_license_start_date_in_millis gauge - elasticsearch_cluster_license_start_date_in_millis{issued_to="redacted",issuer="elasticsearch",status="active",type="basic"} -1 + # HELP elasticsearch_cluster_license_start_date_seconds License start date since unix epoch in seconds. + # TYPE elasticsearch_cluster_license_start_date_seconds gauge + elasticsearch_cluster_license_start_date_seconds{issued_to="redacted",issuer="elasticsearch",status="active",type="basic"} 0 `, }, { name: "platinum", file: "../fixtures/clusterlicense/platinum.json", want: ` - # HELP elasticsearch_cluster_license_expiry_date_in_millis License expiry date in milliseconds - # TYPE elasticsearch_cluster_license_expiry_date_in_millis gauge - elasticsearch_cluster_license_expiry_date_in_millis{issued_to="redacted",issuer="API",status="active",type="platinum"} 1.714521599999e+12 - # HELP elasticsearch_cluster_license_issue_date_in_millis License issue date in milliseconds - # TYPE elasticsearch_cluster_license_issue_date_in_millis gauge - elasticsearch_cluster_license_issue_date_in_millis{issued_to="redacted",issuer="API",status="active",type="platinum"} 1.6192224e+12 - # HELP elasticsearch_cluster_license_max_nodes The max amount of nodes allowed by the license + # HELP elasticsearch_cluster_license_expiry_date_seconds License expiry date since unix epoch in seconds. + # TYPE elasticsearch_cluster_license_expiry_date_seconds gauge + elasticsearch_cluster_license_expiry_date_seconds{issued_to="redacted",issuer="API",status="active",type="platinum"} 1.714521599e+09 + # HELP elasticsearch_cluster_license_issue_date_seconds License issue date since unix epoch in seconds. + # TYPE elasticsearch_cluster_license_issue_date_seconds gauge + elasticsearch_cluster_license_issue_date_seconds{issued_to="redacted",issuer="API",status="active",type="platinum"} 1.6192224e+09 + # HELP elasticsearch_cluster_license_max_nodes The max amount of nodes allowed by the license. # TYPE elasticsearch_cluster_license_max_nodes gauge elasticsearch_cluster_license_max_nodes{issued_to="redacted",issuer="API",status="active",type="platinum"} 10 - # HELP elasticsearch_cluster_license_start_date_in_millis License start date in milliseconds - # TYPE elasticsearch_cluster_license_start_date_in_millis gauge - elasticsearch_cluster_license_start_date_in_millis{issued_to="redacted",issuer="API",status="active",type="platinum"} 1.6192224e+12 + # HELP elasticsearch_cluster_license_start_date_seconds License start date since unix epoch in seconds. + # TYPE elasticsearch_cluster_license_start_date_seconds gauge + elasticsearch_cluster_license_start_date_seconds{issued_to="redacted",issuer="API",status="active",type="platinum"} 1.6192224e+09 `, }, } From 365715f5c93103bdfa8d91e1e93bac1100129716 Mon Sep 17 00:00:00 2001 From: kekkokers Date: Tue, 12 Dec 2023 21:12:09 +0200 Subject: [PATCH 6/7] Add versions to the tests Signed-off-by: kekkokers --- collector/cluster_license_test.go | 8 ++++---- .../clusterlicense/{basic.json => 7.17.10-basic.json} | 0 .../{platinum.json => 7.17.10-platinum.json} | 0 3 files changed, 4 insertions(+), 4 deletions(-) rename fixtures/clusterlicense/{basic.json => 7.17.10-basic.json} (100%) rename fixtures/clusterlicense/{platinum.json => 7.17.10-platinum.json} (100%) diff --git a/collector/cluster_license_test.go b/collector/cluster_license_test.go index 24a880b3..db531bdd 100644 --- a/collector/cluster_license_test.go +++ b/collector/cluster_license_test.go @@ -36,8 +36,8 @@ func TestClusterLicense(t *testing.T) { want string }{ { - name: "basic", - file: "../fixtures/clusterlicense/basic.json", + name: "7.17.10-basic", + file: "../fixtures/clusterlicense/7.17.10-basic.json", want: ` # HELP elasticsearch_cluster_license_expiry_date_seconds License expiry date since unix epoch in seconds. # TYPE elasticsearch_cluster_license_expiry_date_seconds gauge @@ -54,8 +54,8 @@ func TestClusterLicense(t *testing.T) { `, }, { - name: "platinum", - file: "../fixtures/clusterlicense/platinum.json", + name: "7.17.10-platinum", + file: "../fixtures/clusterlicense/7.17.10-platinum.json", want: ` # HELP elasticsearch_cluster_license_expiry_date_seconds License expiry date since unix epoch in seconds. # TYPE elasticsearch_cluster_license_expiry_date_seconds gauge diff --git a/fixtures/clusterlicense/basic.json b/fixtures/clusterlicense/7.17.10-basic.json similarity index 100% rename from fixtures/clusterlicense/basic.json rename to fixtures/clusterlicense/7.17.10-basic.json diff --git a/fixtures/clusterlicense/platinum.json b/fixtures/clusterlicense/7.17.10-platinum.json similarity index 100% rename from fixtures/clusterlicense/platinum.json rename to fixtures/clusterlicense/7.17.10-platinum.json From 7e48099fd9853a6c431485b50dcf665d2bc2ac30 Mon Sep 17 00:00:00 2001 From: kekkokers Date: Wed, 13 Dec 2023 08:53:04 +0200 Subject: [PATCH 7/7] Make flag match others, update README Signed-off-by: kekkokers --- README.md | 4 ++++ collector/cluster_license.go | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 4d22d104..ab73d058 100644 --- a/README.md +++ b/README.md @@ -263,6 +263,10 @@ Further Information | elasticsearch_data_stream_stats_json_parse_failures | counter | 0 | Number of parsing failures for Data Stream stats | | elasticsearch_data_stream_backing_indices_total | gauge | 1 | Number of backing indices for Data Stream | | elasticsearch_data_stream_store_size_bytes | gauge | 1 | Current size of data stream backing indices in bytes | +| elasticsearch_cluster_license_expiry_date_seconds | gauge | 1 | License expiry date since unix epoch in second s | +| elasticsearch_cluster_license_issue_date_seconds | gauge | 1 | License issue date since unix epoch in seconds | +| elasticsearch_cluster_license_max_nodes | gauge | 1 | The max amount of nodes allowed by the license | +| elasticsearch_cluster_license_start_date_seconds | gauge | 1 | License start date since unix epoch in seconds | ### Alerts & Recording Rules diff --git a/collector/cluster_license.go b/collector/cluster_license.go index d156e39a..d0a4b697 100644 --- a/collector/cluster_license.go +++ b/collector/cluster_license.go @@ -75,7 +75,7 @@ var ( ) func init() { - registerCollector("cluster_license", defaultDisabled, NewClusterLicense) + registerCollector("cluster-license", defaultDisabled, NewClusterLicense) } // License Information Struct