m/p/api: use protobuf.Duration in Management.Node
This switches Management.Node message's time_since_heartbeat backing
type from int64 to google.protobuf.Duration in order to enable duration
based predicates in Management.GetNodes filter expressions.
Change-Id: Ia2663475d1b9ee535dc5578f16d53b70c6686b7c
Reviewed-on: https://review.monogon.dev/c/monogon/+/776
Reviewed-by: Lorenz Brun <lorenz@monogon.tech>
Tested-by: Jenkins CI
diff --git a/metropolis/node/core/curator/BUILD.bazel b/metropolis/node/core/curator/BUILD.bazel
index b8ff16a..57ed4ec 100644
--- a/metropolis/node/core/curator/BUILD.bazel
+++ b/metropolis/node/core/curator/BUILD.bazel
@@ -44,6 +44,7 @@
"@org_golang_google_grpc//codes",
"@org_golang_google_grpc//status",
"@org_golang_google_protobuf//proto",
+ "@org_golang_google_protobuf//types/known/durationpb",
],
)
diff --git a/metropolis/node/core/curator/impl_leader_management.go b/metropolis/node/core/curator/impl_leader_management.go
index fd593c3..0c7bc21 100644
--- a/metropolis/node/core/curator/impl_leader_management.go
+++ b/metropolis/node/core/curator/impl_leader_management.go
@@ -9,6 +9,7 @@
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
+ dpb "google.golang.org/protobuf/types/known/durationpb"
"source.monogon.dev/metropolis/node/core/identity"
"source.monogon.dev/metropolis/node/core/rpc"
@@ -215,10 +216,7 @@
State: node.state,
Status: node.status,
Roles: roles,
- // TODO(mateusz@monogon.tech): update the API to use protobuf Duration
- // message, in order to facilitate filter expressions like
- // 'node.HeartbeatTimestamp > duration("30s")'.
- TimeSinceHeartbeat: lhb.Nanoseconds(),
+ TimeSinceHeartbeat: dpb.New(lhb),
Health: health,
}
diff --git a/metropolis/node/core/curator/impl_leader_test.go b/metropolis/node/core/curator/impl_leader_test.go
index 28d5fcf..cc90ca6 100644
--- a/metropolis/node/core/curator/impl_leader_test.go
+++ b/metropolis/node/core/curator/impl_leader_test.go
@@ -1006,6 +1006,28 @@
if exists(in, wnr) {
t.Fatalf("management.GetNodes returned a node which isn't a Kubernetes worker.")
}
+
+ // Exercise duration-based filtering. Start with setting up node and
+ // leadership timestamps much like in TestClusterHeartbeat.
+ tsn := putNode(t, ctx, cl.l, func(n *Node) { n.state = cpb.NodeState_NODE_STATE_UP })
+ nid := identity.NodeID(tsn.pubkey)
+ // Last of node's tsn heartbeats were received 5 seconds ago,
+ cl.l.ls.heartbeatTimestamps.Store(nid, time.Now().Add(-5*time.Second))
+ // ...while the current leader's tenure started 15 seconds ago.
+ cl.l.ls.startTs = time.Now().Add(-15 * time.Second)
+
+ // Get all nodes that sent their last heartbeat between 4 and 6 seconds ago.
+ // Node tsn should be among the results.
+ tsr := getNodes(t, ctx, mgmt, "node.time_since_heartbeat < duration('6s') && node.time_since_heartbeat > duration('4s')")
+ if !exists(tsn, tsr) {
+ t.Fatalf("node was filtered out where it shouldn't be")
+ }
+ // Now, get all nodes that sent their last heartbeat more than 7 seconds ago.
+ // In this case, node tsn should be filtered out.
+ tsr = getNodes(t, ctx, mgmt, "node.time_since_heartbeat > duration('7s')")
+ if exists(tsn, tsr) {
+ t.Fatalf("node wasn't filtered out where it should be")
+ }
}
// TestUpdateNodeRoles exercises management.UpdateNodeRoles by running it
diff --git a/metropolis/proto/api/BUILD.bazel b/metropolis/proto/api/BUILD.bazel
index b732f18..5bc5640 100644
--- a/metropolis/proto/api/BUILD.bazel
+++ b/metropolis/proto/api/BUILD.bazel
@@ -14,6 +14,7 @@
deps = [
"//metropolis/proto/common:common_proto",
"//metropolis/proto/ext:ext_proto",
+ "@com_google_protobuf//:duration_proto",
],
)
diff --git a/metropolis/proto/api/management.proto b/metropolis/proto/api/management.proto
index b48e2b1..d481339 100644
--- a/metropolis/proto/api/management.proto
+++ b/metropolis/proto/api/management.proto
@@ -2,6 +2,8 @@
package metropolis.proto.api;
option go_package = "source.monogon.dev/metropolis/proto/api";
+import "google/protobuf/duration.proto";
+
import "metropolis/proto/common/common.proto";
import "metropolis/proto/ext/authorization.proto";
@@ -140,7 +142,7 @@
// time_since_heartbeat is the duration since the last of the node's
// heartbeats was received, expressed in nanoseconds. It is only valid with
// the health status of either HEALTHY or HEARTBEAT_TIMEOUT.
- int64 time_since_heartbeat = 6;
+ google.protobuf.Duration time_since_heartbeat = 6;
}
message ApproveNodeRequest {