mirror of
https://github.com/MAKS-IT-COM/maksit-cluster-console.git
synced 2026-09-30 00:38:10 +02:00
(bugfix): skip healthy k3s etcd voter node warnings
This commit is contained in:
parent
bff0d5aeef
commit
bf63cf1730
@ -6,9 +6,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [0.1.1] - 2026-08-19
|
||||
|
||||
### Fixed
|
||||
|
||||
- Release engine reads `<Version>` from `src/Directory.Build.props` when the UI csproj has none.
|
||||
- Warnings no longer treat k3s `EtcdIsVoter=True` as unhealthy; only `False` (learner / non-voter) is raised.
|
||||
|
||||
## [0.1.0] - 2026-08-19
|
||||
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
<LangVersion>latest</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Version>0.1.0</Version>
|
||||
<Version>0.1.1</Version>
|
||||
<Product>MaksIT.ClusterConsole</Product>
|
||||
<AssemblyTitle>MaksIT.ClusterConsole</AssemblyTitle>
|
||||
</PropertyGroup>
|
||||
|
||||
@ -28,7 +28,12 @@ public static class ClusterIssues {
|
||||
private static readonly HashSet<string> HealthyNodeConditions = new(StringComparer.Ordinal) {
|
||||
"Ready",
|
||||
"HostUpgrades",
|
||||
"SchedulingDisabled"
|
||||
"SchedulingDisabled",
|
||||
"EtcdIsVoter"
|
||||
};
|
||||
|
||||
private static readonly HashSet<string> UnhealthyWhenFalseNodeConditions = new(StringComparer.Ordinal) {
|
||||
"EtcdIsVoter"
|
||||
};
|
||||
|
||||
public static ClusterIssueSet Collect(
|
||||
@ -92,7 +97,7 @@ public static class ClusterIssues {
|
||||
|
||||
foreach (var condition in conditions.OfType<JsonObject>()) {
|
||||
var type = Text(condition["type"]);
|
||||
if (!IsTrue(condition["status"]) || HealthyNodeConditions.Contains(type))
|
||||
if (!IsUnhealthyNodeCondition(type, condition["status"]))
|
||||
continue;
|
||||
|
||||
var message = Text(condition["message"]);
|
||||
@ -232,12 +237,25 @@ public static class ClusterIssues {
|
||||
? value.TryGetValue<string>(out var text) ? text ?? string.Empty : value.ToString() ?? string.Empty
|
||||
: node?.ToString() ?? string.Empty;
|
||||
|
||||
private static bool IsTrue(JsonNode? node) {
|
||||
private static bool IsUnhealthyNodeCondition(string type, JsonNode? status) {
|
||||
if (UnhealthyWhenFalseNodeConditions.Contains(type))
|
||||
return IsFalse(status);
|
||||
|
||||
return IsTrue(status) && !HealthyNodeConditions.Contains(type);
|
||||
}
|
||||
|
||||
private static bool IsTrue(JsonNode? node) =>
|
||||
IsConditionStatus(node, true, "True");
|
||||
|
||||
private static bool IsFalse(JsonNode? node) =>
|
||||
IsConditionStatus(node, false, "False");
|
||||
|
||||
private static bool IsConditionStatus(JsonNode? node, bool flagValue, string textValue) {
|
||||
if (node is not JsonValue value)
|
||||
return false;
|
||||
if (value.TryGetValue<bool>(out var flag))
|
||||
return flag;
|
||||
return flag == flagValue;
|
||||
return value.TryGetValue<string>(out var text)
|
||||
&& text.Equals("True", StringComparison.OrdinalIgnoreCase);
|
||||
&& text.Equals(textValue, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
|
||||
@ -13,7 +13,8 @@ public class ClusterIssuesTests {
|
||||
var nodes = new[] {
|
||||
Node("k3ssrv0001", "n1", nodeCreated, [
|
||||
Condition("Ready", "True"),
|
||||
Condition("EtcdIsVoter", "True", "Node is a voting member of the etcd cluster")
|
||||
Condition("EtcdIsVoter", "True", "Node is a voting member of the etcd cluster"),
|
||||
Condition("MemoryPressure", "True", "kubelet has memory pressure")
|
||||
])
|
||||
};
|
||||
var events = new[] {
|
||||
@ -27,7 +28,7 @@ public class ClusterIssuesTests {
|
||||
Assert.Empty(set.Errors);
|
||||
var node = Assert.Single(set.Warnings, w => w.Kind == "Node");
|
||||
Assert.Equal("k3ssrv0001", node.ObjectName);
|
||||
Assert.Contains("etcd", node.Message);
|
||||
Assert.Contains("memory pressure", node.Message);
|
||||
Assert.Equal(ClusterIssues.Active, node.State);
|
||||
Assert.Equal("120d", node.Age);
|
||||
|
||||
@ -37,6 +38,44 @@ public class ClusterIssuesTests {
|
||||
Assert.Equal("2h56m", warning.Age);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Collect_skips_healthy_etcd_voter_condition() {
|
||||
var now = DateTimeOffset.Parse("2026-08-19T15:00:00Z");
|
||||
var nodes = new[] {
|
||||
Node("k3ssrv0001", "n1", now.AddDays(-121), [
|
||||
Condition("Ready", "True"),
|
||||
Condition("EtcdIsVoter", "True", "Node is a voting member of the etcd cluster")
|
||||
])
|
||||
};
|
||||
|
||||
var set = ClusterIssues.Collect(nodes, [], [], now);
|
||||
|
||||
Assert.Empty(set.Warnings);
|
||||
Assert.Empty(set.Errors);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Collect_warns_when_etcd_is_not_a_voter() {
|
||||
var now = DateTimeOffset.Parse("2026-08-19T15:00:00Z");
|
||||
var nodeCreated = now.AddDays(-121);
|
||||
var nodes = new[] {
|
||||
Node("k3ssrv0001", "n1", nodeCreated, [
|
||||
Condition("Ready", "True"),
|
||||
Condition("EtcdIsVoter", "False", "this server has not yet been promoted from learner to voting member")
|
||||
])
|
||||
};
|
||||
|
||||
var set = ClusterIssues.Collect(nodes, [], [], now);
|
||||
|
||||
var warning = Assert.Single(set.Warnings);
|
||||
Assert.Equal("Node", warning.Kind);
|
||||
Assert.Equal("k3ssrv0001", warning.ObjectName);
|
||||
Assert.Contains("learner", warning.Message);
|
||||
Assert.Equal(ClusterIssues.Active, warning.State);
|
||||
Assert.Equal("121d", warning.Age);
|
||||
Assert.Empty(set.Errors);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Collect_keeps_latest_warning_per_involved_object() {
|
||||
var now = DateTimeOffset.Parse("2026-08-19T15:00:00Z");
|
||||
|
||||
Loading…
Reference in New Issue
Block a user