mirror of
https://github.com/juanfont/headscale.git
synced 2025-11-07 04:42:52 -05:00
expire nodes with a custom timestamp (#2828)
This commit is contained in:
1
.github/workflows/test-integration.yaml
vendored
1
.github/workflows/test-integration.yaml
vendored
@@ -70,6 +70,7 @@ jobs:
|
|||||||
- TestTaildrop
|
- TestTaildrop
|
||||||
- TestUpdateHostnameFromClient
|
- TestUpdateHostnameFromClient
|
||||||
- TestExpireNode
|
- TestExpireNode
|
||||||
|
- TestSetNodeExpiryInFuture
|
||||||
- TestNodeOnlineStatus
|
- TestNodeOnlineStatus
|
||||||
- TestPingAllByIPManyUpDown
|
- TestPingAllByIPManyUpDown
|
||||||
- Test2118DeletingOnlineNodePanics
|
- Test2118DeletingOnlineNodePanics
|
||||||
|
|||||||
@@ -2,6 +2,11 @@
|
|||||||
|
|
||||||
## Next
|
## Next
|
||||||
|
|
||||||
|
### Changes
|
||||||
|
|
||||||
|
- Expire nodes with a custom timestamp
|
||||||
|
[#2828](https://github.com/juanfont/headscale/pull/2828)
|
||||||
|
|
||||||
## 0.27.0 (2025-10-27)
|
## 0.27.0 (2025-10-27)
|
||||||
|
|
||||||
**Minimum supported Tailscale client version: v1.64.0**
|
**Minimum supported Tailscale client version: v1.64.0**
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import (
|
|||||||
"github.com/samber/lo"
|
"github.com/samber/lo"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"google.golang.org/grpc/status"
|
"google.golang.org/grpc/status"
|
||||||
|
"google.golang.org/protobuf/types/known/timestamppb"
|
||||||
"tailscale.com/types/key"
|
"tailscale.com/types/key"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -51,6 +52,7 @@ func init() {
|
|||||||
nodeCmd.AddCommand(registerNodeCmd)
|
nodeCmd.AddCommand(registerNodeCmd)
|
||||||
|
|
||||||
expireNodeCmd.Flags().Uint64P("identifier", "i", 0, "Node identifier (ID)")
|
expireNodeCmd.Flags().Uint64P("identifier", "i", 0, "Node identifier (ID)")
|
||||||
|
expireNodeCmd.Flags().StringP("expiry", "e", "", "Set expire to (RFC3339 format, e.g. 2025-08-27T10:00:00Z), or leave empty to expire immediately.")
|
||||||
err = expireNodeCmd.MarkFlagRequired("identifier")
|
err = expireNodeCmd.MarkFlagRequired("identifier")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err.Error())
|
log.Fatal(err.Error())
|
||||||
@@ -289,12 +291,37 @@ var expireNodeCmd = &cobra.Command{
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
expiry, err := cmd.Flags().GetString("expiry")
|
||||||
|
if err != nil {
|
||||||
|
ErrorOutput(
|
||||||
|
err,
|
||||||
|
fmt.Sprintf("Error converting expiry to string: %s", err),
|
||||||
|
output,
|
||||||
|
)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
expiryTime := time.Now()
|
||||||
|
if expiry != "" {
|
||||||
|
expiryTime, err = time.Parse(time.RFC3339, expiry)
|
||||||
|
if err != nil {
|
||||||
|
ErrorOutput(
|
||||||
|
err,
|
||||||
|
fmt.Sprintf("Error converting expiry to string: %s", err),
|
||||||
|
output,
|
||||||
|
)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ctx, client, conn, cancel := newHeadscaleCLIWithConfig()
|
ctx, client, conn, cancel := newHeadscaleCLIWithConfig()
|
||||||
defer cancel()
|
defer cancel()
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
|
|
||||||
request := &v1.ExpireNodeRequest{
|
request := &v1.ExpireNodeRequest{
|
||||||
NodeId: identifier,
|
NodeId: identifier,
|
||||||
|
Expiry: timestamppb.New(expiryTime),
|
||||||
}
|
}
|
||||||
|
|
||||||
response, err := client.ExpireNode(ctx, request)
|
response, err := client.ExpireNode(ctx, request)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.36.8
|
// protoc-gen-go v1.36.10
|
||||||
// protoc (unknown)
|
// protoc (unknown)
|
||||||
// source: headscale/v1/apikey.proto
|
// source: headscale/v1/apikey.proto
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.36.8
|
// protoc-gen-go v1.36.10
|
||||||
// protoc (unknown)
|
// protoc (unknown)
|
||||||
// source: headscale/v1/device.proto
|
// source: headscale/v1/device.proto
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.36.8
|
// protoc-gen-go v1.36.10
|
||||||
// protoc (unknown)
|
// protoc (unknown)
|
||||||
// source: headscale/v1/headscale.proto
|
// source: headscale/v1/headscale.proto
|
||||||
|
|
||||||
|
|||||||
@@ -471,6 +471,8 @@ func local_request_HeadscaleService_DeleteNode_0(ctx context.Context, marshaler
|
|||||||
return msg, metadata, err
|
return msg, metadata, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var filter_HeadscaleService_ExpireNode_0 = &utilities.DoubleArray{Encoding: map[string]int{"node_id": 0}, Base: []int{1, 1, 0}, Check: []int{0, 1, 2}}
|
||||||
|
|
||||||
func request_HeadscaleService_ExpireNode_0(ctx context.Context, marshaler runtime.Marshaler, client HeadscaleServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
func request_HeadscaleService_ExpireNode_0(ctx context.Context, marshaler runtime.Marshaler, client HeadscaleServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||||
var (
|
var (
|
||||||
protoReq ExpireNodeRequest
|
protoReq ExpireNodeRequest
|
||||||
@@ -485,6 +487,12 @@ func request_HeadscaleService_ExpireNode_0(ctx context.Context, marshaler runtim
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "node_id", err)
|
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "node_id", err)
|
||||||
}
|
}
|
||||||
|
if err := req.ParseForm(); err != nil {
|
||||||
|
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||||
|
}
|
||||||
|
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_HeadscaleService_ExpireNode_0); err != nil {
|
||||||
|
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||||
|
}
|
||||||
msg, err := client.ExpireNode(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
msg, err := client.ExpireNode(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||||
return msg, metadata, err
|
return msg, metadata, err
|
||||||
}
|
}
|
||||||
@@ -503,6 +511,12 @@ func local_request_HeadscaleService_ExpireNode_0(ctx context.Context, marshaler
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "node_id", err)
|
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "node_id", err)
|
||||||
}
|
}
|
||||||
|
if err := req.ParseForm(); err != nil {
|
||||||
|
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||||
|
}
|
||||||
|
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_HeadscaleService_ExpireNode_0); err != nil {
|
||||||
|
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||||
|
}
|
||||||
msg, err := server.ExpireNode(ctx, &protoReq)
|
msg, err := server.ExpireNode(ctx, &protoReq)
|
||||||
return msg, metadata, err
|
return msg, metadata, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.36.8
|
// protoc-gen-go v1.36.10
|
||||||
// protoc (unknown)
|
// protoc (unknown)
|
||||||
// source: headscale/v1/node.proto
|
// source: headscale/v1/node.proto
|
||||||
|
|
||||||
@@ -729,6 +729,7 @@ func (*DeleteNodeResponse) Descriptor() ([]byte, []int) {
|
|||||||
type ExpireNodeRequest struct {
|
type ExpireNodeRequest struct {
|
||||||
state protoimpl.MessageState `protogen:"open.v1"`
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
NodeId uint64 `protobuf:"varint,1,opt,name=node_id,json=nodeId,proto3" json:"node_id,omitempty"`
|
NodeId uint64 `protobuf:"varint,1,opt,name=node_id,json=nodeId,proto3" json:"node_id,omitempty"`
|
||||||
|
Expiry *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=expiry,proto3" json:"expiry,omitempty"`
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
}
|
}
|
||||||
@@ -770,6 +771,13 @@ func (x *ExpireNodeRequest) GetNodeId() uint64 {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (x *ExpireNodeRequest) GetExpiry() *timestamppb.Timestamp {
|
||||||
|
if x != nil {
|
||||||
|
return x.Expiry
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
type ExpireNodeResponse struct {
|
type ExpireNodeResponse struct {
|
||||||
state protoimpl.MessageState `protogen:"open.v1"`
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
Node *Node `protobuf:"bytes,1,opt,name=node,proto3" json:"node,omitempty"`
|
Node *Node `protobuf:"bytes,1,opt,name=node,proto3" json:"node,omitempty"`
|
||||||
@@ -1349,9 +1357,10 @@ const file_headscale_v1_node_proto_rawDesc = "" +
|
|||||||
"\x04node\x18\x01 \x01(\v2\x12.headscale.v1.NodeR\x04node\",\n" +
|
"\x04node\x18\x01 \x01(\v2\x12.headscale.v1.NodeR\x04node\",\n" +
|
||||||
"\x11DeleteNodeRequest\x12\x17\n" +
|
"\x11DeleteNodeRequest\x12\x17\n" +
|
||||||
"\anode_id\x18\x01 \x01(\x04R\x06nodeId\"\x14\n" +
|
"\anode_id\x18\x01 \x01(\x04R\x06nodeId\"\x14\n" +
|
||||||
"\x12DeleteNodeResponse\",\n" +
|
"\x12DeleteNodeResponse\"`\n" +
|
||||||
"\x11ExpireNodeRequest\x12\x17\n" +
|
"\x11ExpireNodeRequest\x12\x17\n" +
|
||||||
"\anode_id\x18\x01 \x01(\x04R\x06nodeId\"<\n" +
|
"\anode_id\x18\x01 \x01(\x04R\x06nodeId\x122\n" +
|
||||||
|
"\x06expiry\x18\x02 \x01(\v2\x1a.google.protobuf.TimestampR\x06expiry\"<\n" +
|
||||||
"\x12ExpireNodeResponse\x12&\n" +
|
"\x12ExpireNodeResponse\x12&\n" +
|
||||||
"\x04node\x18\x01 \x01(\v2\x12.headscale.v1.NodeR\x04node\"G\n" +
|
"\x04node\x18\x01 \x01(\v2\x12.headscale.v1.NodeR\x04node\"G\n" +
|
||||||
"\x11RenameNodeRequest\x12\x17\n" +
|
"\x11RenameNodeRequest\x12\x17\n" +
|
||||||
@@ -1439,16 +1448,17 @@ var file_headscale_v1_node_proto_depIdxs = []int32{
|
|||||||
1, // 7: headscale.v1.GetNodeResponse.node:type_name -> headscale.v1.Node
|
1, // 7: headscale.v1.GetNodeResponse.node:type_name -> headscale.v1.Node
|
||||||
1, // 8: headscale.v1.SetTagsResponse.node:type_name -> headscale.v1.Node
|
1, // 8: headscale.v1.SetTagsResponse.node:type_name -> headscale.v1.Node
|
||||||
1, // 9: headscale.v1.SetApprovedRoutesResponse.node:type_name -> headscale.v1.Node
|
1, // 9: headscale.v1.SetApprovedRoutesResponse.node:type_name -> headscale.v1.Node
|
||||||
1, // 10: headscale.v1.ExpireNodeResponse.node:type_name -> headscale.v1.Node
|
25, // 10: headscale.v1.ExpireNodeRequest.expiry:type_name -> google.protobuf.Timestamp
|
||||||
1, // 11: headscale.v1.RenameNodeResponse.node:type_name -> headscale.v1.Node
|
1, // 11: headscale.v1.ExpireNodeResponse.node:type_name -> headscale.v1.Node
|
||||||
1, // 12: headscale.v1.ListNodesResponse.nodes:type_name -> headscale.v1.Node
|
1, // 12: headscale.v1.RenameNodeResponse.node:type_name -> headscale.v1.Node
|
||||||
1, // 13: headscale.v1.MoveNodeResponse.node:type_name -> headscale.v1.Node
|
1, // 13: headscale.v1.ListNodesResponse.nodes:type_name -> headscale.v1.Node
|
||||||
1, // 14: headscale.v1.DebugCreateNodeResponse.node:type_name -> headscale.v1.Node
|
1, // 14: headscale.v1.MoveNodeResponse.node:type_name -> headscale.v1.Node
|
||||||
15, // [15:15] is the sub-list for method output_type
|
1, // 15: headscale.v1.DebugCreateNodeResponse.node:type_name -> headscale.v1.Node
|
||||||
15, // [15:15] is the sub-list for method input_type
|
16, // [16:16] is the sub-list for method output_type
|
||||||
15, // [15:15] is the sub-list for extension type_name
|
16, // [16:16] is the sub-list for method input_type
|
||||||
15, // [15:15] is the sub-list for extension extendee
|
16, // [16:16] is the sub-list for extension type_name
|
||||||
0, // [0:15] is the sub-list for field type_name
|
16, // [16:16] is the sub-list for extension extendee
|
||||||
|
0, // [0:16] is the sub-list for field type_name
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() { file_headscale_v1_node_proto_init() }
|
func init() { file_headscale_v1_node_proto_init() }
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.36.8
|
// protoc-gen-go v1.36.10
|
||||||
// protoc (unknown)
|
// protoc (unknown)
|
||||||
// source: headscale/v1/policy.proto
|
// source: headscale/v1/policy.proto
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.36.8
|
// protoc-gen-go v1.36.10
|
||||||
// protoc (unknown)
|
// protoc (unknown)
|
||||||
// source: headscale/v1/preauthkey.proto
|
// source: headscale/v1/preauthkey.proto
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.36.8
|
// protoc-gen-go v1.36.10
|
||||||
// protoc (unknown)
|
// protoc (unknown)
|
||||||
// source: headscale/v1/user.proto
|
// source: headscale/v1/user.proto
|
||||||
|
|
||||||
|
|||||||
@@ -406,6 +406,13 @@
|
|||||||
"required": true,
|
"required": true,
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"format": "uint64"
|
"format": "uint64"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "expiry",
|
||||||
|
"in": "query",
|
||||||
|
"required": false,
|
||||||
|
"type": "string",
|
||||||
|
"format": "date-time"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"tags": [
|
"tags": [
|
||||||
|
|||||||
@@ -27,9 +27,7 @@ const (
|
|||||||
NodeGivenNameTrimSize = 2
|
NodeGivenNameTrimSize = 2
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var invalidDNSRegex = regexp.MustCompile("[^a-z0-9-.]+")
|
||||||
invalidDNSRegex = regexp.MustCompile("[^a-z0-9-.]+")
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrNodeNotFound = errors.New("node not found")
|
ErrNodeNotFound = errors.New("node not found")
|
||||||
|
|||||||
@@ -416,9 +416,12 @@ func (api headscaleV1APIServer) ExpireNode(
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
request *v1.ExpireNodeRequest,
|
request *v1.ExpireNodeRequest,
|
||||||
) (*v1.ExpireNodeResponse, error) {
|
) (*v1.ExpireNodeResponse, error) {
|
||||||
now := time.Now()
|
expiry := time.Now()
|
||||||
|
if request.GetExpiry() != nil {
|
||||||
|
expiry = request.GetExpiry().AsTime()
|
||||||
|
}
|
||||||
|
|
||||||
node, nodeChange, err := api.h.state.SetNodeExpiry(types.NodeID(request.GetNodeId()), now)
|
node, nodeChange, err := api.h.state.SetNodeExpiry(types.NodeID(request.GetNodeId()), expiry)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -819,6 +819,104 @@ func TestExpireNode(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestSetNodeExpiryInFuture tests setting arbitrary expiration date
|
||||||
|
// New expiration date should be stored in the db and propagated to all peers
|
||||||
|
func TestSetNodeExpiryInFuture(t *testing.T) {
|
||||||
|
IntegrationSkip(t)
|
||||||
|
|
||||||
|
spec := ScenarioSpec{
|
||||||
|
NodesPerUser: len(MustTestVersions),
|
||||||
|
Users: []string{"user1"},
|
||||||
|
}
|
||||||
|
|
||||||
|
scenario, err := NewScenario(spec)
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer scenario.ShutdownAssertNoPanics(t)
|
||||||
|
|
||||||
|
err = scenario.CreateHeadscaleEnv([]tsic.Option{}, hsic.WithTestName("expirenodefuture"))
|
||||||
|
requireNoErrHeadscaleEnv(t, err)
|
||||||
|
|
||||||
|
allClients, err := scenario.ListTailscaleClients()
|
||||||
|
requireNoErrListClients(t, err)
|
||||||
|
|
||||||
|
err = scenario.WaitForTailscaleSync()
|
||||||
|
requireNoErrSync(t, err)
|
||||||
|
|
||||||
|
headscale, err := scenario.Headscale()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
targetExpiry := time.Now().Add(2 * time.Hour).Round(time.Second).UTC()
|
||||||
|
|
||||||
|
result, err := headscale.Execute(
|
||||||
|
[]string{
|
||||||
|
"headscale", "nodes", "expire",
|
||||||
|
"--identifier", "1",
|
||||||
|
"--output", "json",
|
||||||
|
"--expiry", targetExpiry.Format(time.RFC3339),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
var node v1.Node
|
||||||
|
err = json.Unmarshal([]byte(result), &node)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.True(t, node.GetExpiry().AsTime().After(time.Now()))
|
||||||
|
require.WithinDuration(t, targetExpiry, node.GetExpiry().AsTime(), 2*time.Second)
|
||||||
|
|
||||||
|
var nodeKey key.NodePublic
|
||||||
|
err = nodeKey.UnmarshalText([]byte(node.GetNodeKey()))
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
for _, client := range allClients {
|
||||||
|
if client.Hostname() == node.GetName() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.EventuallyWithT(
|
||||||
|
t, func(ct *assert.CollectT) {
|
||||||
|
status, err := client.Status()
|
||||||
|
assert.NoError(ct, err)
|
||||||
|
|
||||||
|
peerStatus, ok := status.Peer[nodeKey]
|
||||||
|
assert.True(ct, ok, "node key should be present in peer list")
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.NotNil(ct, peerStatus.KeyExpiry)
|
||||||
|
assert.NotNil(ct, peerStatus.Expired)
|
||||||
|
|
||||||
|
if peerStatus.KeyExpiry != nil {
|
||||||
|
assert.WithinDuration(
|
||||||
|
ct,
|
||||||
|
targetExpiry,
|
||||||
|
*peerStatus.KeyExpiry,
|
||||||
|
5*time.Second,
|
||||||
|
"node %q should have key expiry near the requested future time",
|
||||||
|
peerStatus.HostName,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert.Truef(
|
||||||
|
ct,
|
||||||
|
peerStatus.KeyExpiry.After(time.Now()),
|
||||||
|
"node %q should have a key expiry timestamp in the future",
|
||||||
|
peerStatus.HostName,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Falsef(
|
||||||
|
ct,
|
||||||
|
peerStatus.Expired,
|
||||||
|
"node %q should not be marked as expired",
|
||||||
|
peerStatus.HostName,
|
||||||
|
)
|
||||||
|
}, 3*time.Minute, 5*time.Second, "Waiting for future expiry to propagate",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestNodeOnlineStatus(t *testing.T) {
|
func TestNodeOnlineStatus(t *testing.T) {
|
||||||
IntegrationSkip(t)
|
IntegrationSkip(t)
|
||||||
|
|
||||||
|
|||||||
@@ -82,7 +82,10 @@ message DeleteNodeRequest { uint64 node_id = 1; }
|
|||||||
|
|
||||||
message DeleteNodeResponse {}
|
message DeleteNodeResponse {}
|
||||||
|
|
||||||
message ExpireNodeRequest { uint64 node_id = 1; }
|
message ExpireNodeRequest {
|
||||||
|
uint64 node_id = 1;
|
||||||
|
google.protobuf.Timestamp expiry = 2;
|
||||||
|
}
|
||||||
|
|
||||||
message ExpireNodeResponse { Node node = 1; }
|
message ExpireNodeResponse { Node node = 1; }
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user