-
Notifications
You must be signed in to change notification settings - Fork 592
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
CORE-7338 license: add enterprise_license_expiry_sec
metric
#23367
Changes from all commits
78de1c7
1c8f66b
edb5f48
c44d9d8
baece2c
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 |
---|---|---|
|
@@ -15,12 +15,18 @@ | |
#include "cluster/version.h" | ||
#include "config/configuration.h" | ||
#include "features/logger.h" | ||
#include "metrics/metrics.h" | ||
#include "metrics/prometheus_sanitize.h" | ||
#include "version/version.h" | ||
|
||
#include <seastar/core/abort_source.hh> | ||
|
||
#include <chrono> | ||
#include <memory> | ||
|
||
// The feature table is closely related to cluster and uses many types from it | ||
using namespace cluster; | ||
using namespace std::chrono_literals; | ||
|
||
namespace features { | ||
|
||
|
@@ -200,6 +206,60 @@ static std::array test_extra_schema{ | |
feature_spec::prepare_policy::always}, | ||
}; | ||
|
||
class feature_table::probe { | ||
public: | ||
explicit probe(const feature_table& parent) | ||
: _parent(parent) {} | ||
|
||
probe(const probe&) = delete; | ||
probe& operator=(const probe&) = delete; | ||
probe(probe&&) = delete; | ||
probe& operator=(probe&&) = delete; | ||
~probe() noexcept = default; | ||
|
||
void setup_metrics() { | ||
if (ss::this_shard_id() != 0) { | ||
return; | ||
} | ||
|
||
if (!config::shard_local_cfg().disable_metrics()) { | ||
setup_metrics_for(_metrics); | ||
} | ||
|
||
if (!config::shard_local_cfg().disable_public_metrics()) { | ||
setup_metrics_for(_public_metrics); | ||
} | ||
} | ||
|
||
void setup_metrics_for(metrics::metric_groups_base& metrics) { | ||
namespace sm = ss::metrics; | ||
|
||
static_assert( | ||
!std::is_move_constructible_v<feature_table> | ||
&& !std::is_move_assignable_v<feature_table> | ||
&& !std::is_copy_constructible_v<feature_table> | ||
&& !std::is_copy_assignable_v<feature_table>, | ||
"The probe captures a reference to this"); | ||
|
||
metrics.add_group( | ||
prometheus_sanitize::metrics_name("cluster:features"), | ||
{ | ||
sm::make_gauge( | ||
"enterprise_license_expiry_sec", | ||
[&ft = _parent]() { | ||
return calculate_expiry_metric(ft.get_license()); | ||
}, | ||
sm::description("Number of seconds remaining until the " | ||
"Enterprise license expires")) | ||
.aggregate({sm::shard_label}), | ||
}); | ||
} | ||
|
||
const feature_table& _parent; | ||
metrics::internal_metric_groups _metrics; | ||
metrics::public_metric_groups _public_metrics; | ||
}; | ||
|
||
feature_table::feature_table() { | ||
// Intentionally undocumented environment variable, only for use | ||
// in integration tests. | ||
|
@@ -232,9 +292,15 @@ feature_table::feature_table() { | |
} | ||
} | ||
} | ||
|
||
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. declaring the destructor made this true, but I wonder if it's worth being explicit: static_assert(
!std::is_move_constructible_v<feature_table>
&& !std::is_move_assignable_v<feature_table>
&& !std::is_copy_constructible_v<feature_table>
&& !std::is_copy_assignable_v<feature_table>,
"The probe captures a reference to this"); 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. Yeah I like it, I've adopted this now |
||
_probe = std::make_unique<probe>(*this); | ||
_probe->setup_metrics(); | ||
} | ||
|
||
feature_table::~feature_table() noexcept = default; | ||
|
||
ss::future<> feature_table::stop() { | ||
_probe.reset(); | ||
_as.request_abort(); | ||
|
||
// Don't trust callers to have fired their abort source in the right | ||
|
@@ -697,6 +763,18 @@ void feature_table::assert_compatible_version(bool override) { | |
} | ||
} | ||
|
||
long long feature_table::calculate_expiry_metric( | ||
const std::optional<security::license>& license, | ||
security::license::clock::time_point now) { | ||
if (!license) { | ||
return -1; | ||
} | ||
Comment on lines
+769
to
+771
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. discussion: If there is no license, it may be possible to not set up this metric (or even the probe), but then setting up the probe or metric must be attached to For the latter, it may be worth: diff --git a/src/v/features/feature_table_snapshot.cc b/src/v/features/feature_table_snapshot.cc
index 6e1cfb406e..b5477a88c5 100644
--- a/src/v/features/feature_table_snapshot.cc
+++ b/src/v/features/feature_table_snapshot.cc
@@ -42,7 +42,11 @@ void feature_table_snapshot::apply(feature_table& ft) const {
applied_offset);
ft.set_active_version(version);
- ft._license = license;
+ if (license) {
+ ft.set_license(*license);
+ } else {
+ ft.revoke_license();
+ } But I'm also OK with returning -1. 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. Yeah, that's a good point to discuss but I've kept this as is for now to keep things simpler. I kinda like having the -1 explicitly present to signal that the license it unset (as opposed to just the metric missing because of network issues, Prometheus scraper being down, etc.). But I don't feel strongly either way which makes me prefer simplicity. 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.
I kinda like that. But yeah, doesn't seem too critical |
||
|
||
auto rem = license->expiration() - now; | ||
auto rem_capped = std::max(rem.zero(), rem); | ||
return rem_capped / 1s; | ||
} | ||
|
||
} // namespace features | ||
|
||
namespace cluster { | ||
|
Original file line number | Diff line number | Diff line change | ||||||
---|---|---|---|---|---|---|---|---|
|
@@ -17,6 +17,7 @@ | |||||||
#include "utils/waiter_queue.h" | ||||||||
|
||||||||
#include <array> | ||||||||
#include <memory> | ||||||||
#include <string_view> | ||||||||
#include <unordered_set> | ||||||||
|
||||||||
|
@@ -497,6 +498,11 @@ class feature_table { | |||||||
static cluster::cluster_version get_earliest_logical_version(); | ||||||||
|
||||||||
feature_table(); | ||||||||
oleiman marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||
feature_table(const feature_table&) = delete; | ||||||||
feature_table& operator=(const feature_table&) = delete; | ||||||||
feature_table(feature_table&&) = delete; | ||||||||
feature_table& operator=(feature_table&&) = delete; | ||||||||
~feature_table() noexcept; | ||||||||
|
||||||||
feature_state& get_state(feature f_id); | ||||||||
const feature_state& get_state(feature f_id) const { | ||||||||
|
@@ -599,7 +605,15 @@ class feature_table { | |||||||
// Assert out on startup if we appear to have upgraded too far | ||||||||
void assert_compatible_version(bool); | ||||||||
|
||||||||
// Visible for testing | ||||||||
static long long calculate_expiry_metric( | ||||||||
const std::optional<security::license>& license, | ||||||||
security::license::clock::time_point now | ||||||||
= security::license::clock::now()); | ||||||||
|
||||||||
private: | ||||||||
class probe; | ||||||||
|
||||||||
// Only for use by our friends feature backend & manager | ||||||||
void set_active_version( | ||||||||
cluster::cluster_version, | ||||||||
|
@@ -655,6 +669,7 @@ class feature_table { | |||||||
|
||||||||
ss::gate _gate; | ||||||||
ss::abort_source _as; | ||||||||
std::unique_ptr<probe> _probe; | ||||||||
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. Holding the probes by pointer is useful when the owning class:
Even if neither of these were true, holding it by pointer is still useful as a compiler firewall; if In order to keep your tests, you'll have to move Please destroy the probe in The probe is constructed in
Suggested change
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. Thanks for the detailed explanation, very useful. I've changed this now to have the probe be forward declared and its implementation hidden inside the implementation file. |
||||||||
}; | ||||||||
|
||||||||
} // namespace features | ||||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
load("//bazel:test.bzl", "redpanda_cc_btest") | ||
|
||
redpanda_cc_btest( | ||
name = "feature_table_test", | ||
timeout = "short", | ||
srcs = [ | ||
"feature_table_test.cc", | ||
], | ||
deps = [ | ||
"//src/v/cluster:features", | ||
"//src/v/features", | ||
"//src/v/security:license", | ||
"//src/v/test_utils:seastar_boost", | ||
"@seastar", | ||
"@seastar//:testing", | ||
], | ||
) |
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.
My idea was so that you didn't need the
_parent
member.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.
Right, now I get what you meant there, that makes more sense. I will leave this as is now since the behaviour is pretty much the same.