-
Notifications
You must be signed in to change notification settings - Fork 795
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: add cluster license collector #833
base: master
Are you sure you want to change the base?
Changes from all commits
617e127
0c74085
728e447
b4149e1
e283dcc
365715f
7e48099
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,159 @@ | ||
// 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 ( | ||
"context" | ||
"encoding/json" | ||
"fmt" | ||
"io" | ||
"net/http" | ||
"net/url" | ||
"path" | ||
"time" | ||
|
||
"github.com/go-kit/log" | ||
"github.com/go-kit/log/level" | ||
"github.com/prometheus/client_golang/prometheus" | ||
) | ||
|
||
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{"issued_to", "issuer", "type", "status"} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @SuperQ What do you think of these labels? |
||
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_seconds"), | ||
"License issue date since unix epoch in seconds.", | ||
defaultClusterLicenseLabels, nil, | ||
) | ||
licenseExpiryDate = prometheus.NewDesc( | ||
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_seconds"), | ||
"License start date since unix epoch in seconds.", | ||
defaultClusterLicenseLabels, nil, | ||
) | ||
) | ||
|
||
func init() { | ||
registerCollector("cluster-license", defaultDisabled, NewClusterLicense) | ||
} | ||
|
||
// License Information Struct | ||
type ClusterLicense struct { | ||
logger log.Logger | ||
hc *http.Client | ||
u *url.URL | ||
} | ||
|
||
func NewClusterLicense(logger log.Logger, u *url.URL, hc *http.Client) (Collector, error) { | ||
return &ClusterLicense{ | ||
logger: logger, | ||
u: u, | ||
hc: hc, | ||
}, nil | ||
} | ||
|
||
func (c *ClusterLicense) Update(ctx context.Context, ch chan<- prometheus.Metric) error { | ||
var clr clusterLicenseResponse | ||
|
||
u := *c.u | ||
u.Path = path.Join(u.Path, "/_license") | ||
res, err := c.hc.Get(u.String()) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Most of this code can be removed and use the https://github.com/prometheus-community/elasticsearch_exporter/blob/master/collector/util.go#L26 |
||
|
||
if err != nil { | ||
return err | ||
} | ||
|
||
defer func() { | ||
err = res.Body.Close() | ||
if err != nil { | ||
level.Warn(c.logger).Log( | ||
"msg", "failed to close http.Client", | ||
"err", err, | ||
) | ||
} | ||
}() | ||
|
||
if res.StatusCode != http.StatusOK { | ||
return fmt.Errorf("HTTP Request failed with code %d", res.StatusCode) | ||
} | ||
|
||
bts, err := io.ReadAll(res.Body) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
if err := json.Unmarshal(bts, &clr); err != nil { | ||
return err | ||
} | ||
|
||
ch <- prometheus.MustNewConstMetric( | ||
licenseMaxNodes, | ||
prometheus.GaugeValue, | ||
float64(clr.License.MaxNodes), | ||
defaultClusterLicenseLabelsValues(clr)..., | ||
) | ||
|
||
ch <- prometheus.MustNewConstMetric( | ||
licenseIssueDate, | ||
prometheus.GaugeValue, | ||
float64(clr.License.IssueDateInMillis/1000), | ||
defaultClusterLicenseLabelsValues(clr)..., | ||
) | ||
|
||
ch <- prometheus.MustNewConstMetric( | ||
licenseExpiryDate, | ||
prometheus.GaugeValue, | ||
float64(clr.License.ExpiryDateInMillis/1000), | ||
defaultClusterLicenseLabelsValues(clr)..., | ||
) | ||
|
||
ch <- prometheus.MustNewConstMetric( | ||
licenseStartDate, | ||
prometheus.GaugeValue, | ||
float64(clr.License.StartDateInMillis/1000), | ||
defaultClusterLicenseLabelsValues(clr)..., | ||
) | ||
|
||
return nil | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
// 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 TestClusterLicense(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: "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 | ||
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_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: "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 | ||
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_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 | ||
`, | ||
}, | ||
} | ||
|
||
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, err := NewClusterLicense(log.NewNopLogger(), u, http.DefaultClient) | ||
|
||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
if err := testutil.CollectAndCompare(wrapCollector{c}, strings.NewReader(tt.want)); err != nil { | ||
t.Fatal(err) | ||
} | ||
}) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.