flwr-nightly 1.23.0.dev20251027__py3-none-any.whl → 1.23.0.dev20251029__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of flwr-nightly might be problematic. Click here for more details.

@@ -28,6 +28,26 @@ class FleetStub(object):
28
28
  request_serializer=flwr_dot_proto_dot_fleet__pb2.DeleteNodeRequest.SerializeToString,
29
29
  response_deserializer=flwr_dot_proto_dot_fleet__pb2.DeleteNodeResponse.FromString,
30
30
  )
31
+ self.RegisterNode = channel.unary_unary(
32
+ '/flwr.proto.Fleet/RegisterNode',
33
+ request_serializer=flwr_dot_proto_dot_fleet__pb2.RegisterNodeFleetRequest.SerializeToString,
34
+ response_deserializer=flwr_dot_proto_dot_fleet__pb2.RegisterNodeFleetResponse.FromString,
35
+ )
36
+ self.ActivateNode = channel.unary_unary(
37
+ '/flwr.proto.Fleet/ActivateNode',
38
+ request_serializer=flwr_dot_proto_dot_fleet__pb2.ActivateNodeRequest.SerializeToString,
39
+ response_deserializer=flwr_dot_proto_dot_fleet__pb2.ActivateNodeResponse.FromString,
40
+ )
41
+ self.DeactivateNode = channel.unary_unary(
42
+ '/flwr.proto.Fleet/DeactivateNode',
43
+ request_serializer=flwr_dot_proto_dot_fleet__pb2.DeactivateNodeRequest.SerializeToString,
44
+ response_deserializer=flwr_dot_proto_dot_fleet__pb2.DeactivateNodeResponse.FromString,
45
+ )
46
+ self.UnregisterNode = channel.unary_unary(
47
+ '/flwr.proto.Fleet/UnregisterNode',
48
+ request_serializer=flwr_dot_proto_dot_fleet__pb2.UnregisterNodeFleetRequest.SerializeToString,
49
+ response_deserializer=flwr_dot_proto_dot_fleet__pb2.UnregisterNodeFleetResponse.FromString,
50
+ )
31
51
  self.SendNodeHeartbeat = channel.unary_unary(
32
52
  '/flwr.proto.Fleet/SendNodeHeartbeat',
33
53
  request_serializer=flwr_dot_proto_dot_heartbeat__pb2.SendNodeHeartbeatRequest.SerializeToString,
@@ -85,6 +105,34 @@ class FleetServicer(object):
85
105
  context.set_details('Method not implemented!')
86
106
  raise NotImplementedError('Method not implemented!')
87
107
 
108
+ def RegisterNode(self, request, context):
109
+ """Register Node
110
+ """
111
+ context.set_code(grpc.StatusCode.UNIMPLEMENTED)
112
+ context.set_details('Method not implemented!')
113
+ raise NotImplementedError('Method not implemented!')
114
+
115
+ def ActivateNode(self, request, context):
116
+ """Activate Node
117
+ """
118
+ context.set_code(grpc.StatusCode.UNIMPLEMENTED)
119
+ context.set_details('Method not implemented!')
120
+ raise NotImplementedError('Method not implemented!')
121
+
122
+ def DeactivateNode(self, request, context):
123
+ """Deactivate Node
124
+ """
125
+ context.set_code(grpc.StatusCode.UNIMPLEMENTED)
126
+ context.set_details('Method not implemented!')
127
+ raise NotImplementedError('Method not implemented!')
128
+
129
+ def UnregisterNode(self, request, context):
130
+ """Unregister Node
131
+ """
132
+ context.set_code(grpc.StatusCode.UNIMPLEMENTED)
133
+ context.set_details('Method not implemented!')
134
+ raise NotImplementedError('Method not implemented!')
135
+
88
136
  def SendNodeHeartbeat(self, request, context):
89
137
  """Missing associated documentation comment in .proto file."""
90
138
  context.set_code(grpc.StatusCode.UNIMPLEMENTED)
@@ -156,6 +204,26 @@ def add_FleetServicer_to_server(servicer, server):
156
204
  request_deserializer=flwr_dot_proto_dot_fleet__pb2.DeleteNodeRequest.FromString,
157
205
  response_serializer=flwr_dot_proto_dot_fleet__pb2.DeleteNodeResponse.SerializeToString,
158
206
  ),
207
+ 'RegisterNode': grpc.unary_unary_rpc_method_handler(
208
+ servicer.RegisterNode,
209
+ request_deserializer=flwr_dot_proto_dot_fleet__pb2.RegisterNodeFleetRequest.FromString,
210
+ response_serializer=flwr_dot_proto_dot_fleet__pb2.RegisterNodeFleetResponse.SerializeToString,
211
+ ),
212
+ 'ActivateNode': grpc.unary_unary_rpc_method_handler(
213
+ servicer.ActivateNode,
214
+ request_deserializer=flwr_dot_proto_dot_fleet__pb2.ActivateNodeRequest.FromString,
215
+ response_serializer=flwr_dot_proto_dot_fleet__pb2.ActivateNodeResponse.SerializeToString,
216
+ ),
217
+ 'DeactivateNode': grpc.unary_unary_rpc_method_handler(
218
+ servicer.DeactivateNode,
219
+ request_deserializer=flwr_dot_proto_dot_fleet__pb2.DeactivateNodeRequest.FromString,
220
+ response_serializer=flwr_dot_proto_dot_fleet__pb2.DeactivateNodeResponse.SerializeToString,
221
+ ),
222
+ 'UnregisterNode': grpc.unary_unary_rpc_method_handler(
223
+ servicer.UnregisterNode,
224
+ request_deserializer=flwr_dot_proto_dot_fleet__pb2.UnregisterNodeFleetRequest.FromString,
225
+ response_serializer=flwr_dot_proto_dot_fleet__pb2.UnregisterNodeFleetResponse.SerializeToString,
226
+ ),
159
227
  'SendNodeHeartbeat': grpc.unary_unary_rpc_method_handler(
160
228
  servicer.SendNodeHeartbeat,
161
229
  request_deserializer=flwr_dot_proto_dot_heartbeat__pb2.SendNodeHeartbeatRequest.FromString,
@@ -240,6 +308,74 @@ class Fleet(object):
240
308
  options, channel_credentials,
241
309
  insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
242
310
 
311
+ @staticmethod
312
+ def RegisterNode(request,
313
+ target,
314
+ options=(),
315
+ channel_credentials=None,
316
+ call_credentials=None,
317
+ insecure=False,
318
+ compression=None,
319
+ wait_for_ready=None,
320
+ timeout=None,
321
+ metadata=None):
322
+ return grpc.experimental.unary_unary(request, target, '/flwr.proto.Fleet/RegisterNode',
323
+ flwr_dot_proto_dot_fleet__pb2.RegisterNodeFleetRequest.SerializeToString,
324
+ flwr_dot_proto_dot_fleet__pb2.RegisterNodeFleetResponse.FromString,
325
+ options, channel_credentials,
326
+ insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
327
+
328
+ @staticmethod
329
+ def ActivateNode(request,
330
+ target,
331
+ options=(),
332
+ channel_credentials=None,
333
+ call_credentials=None,
334
+ insecure=False,
335
+ compression=None,
336
+ wait_for_ready=None,
337
+ timeout=None,
338
+ metadata=None):
339
+ return grpc.experimental.unary_unary(request, target, '/flwr.proto.Fleet/ActivateNode',
340
+ flwr_dot_proto_dot_fleet__pb2.ActivateNodeRequest.SerializeToString,
341
+ flwr_dot_proto_dot_fleet__pb2.ActivateNodeResponse.FromString,
342
+ options, channel_credentials,
343
+ insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
344
+
345
+ @staticmethod
346
+ def DeactivateNode(request,
347
+ target,
348
+ options=(),
349
+ channel_credentials=None,
350
+ call_credentials=None,
351
+ insecure=False,
352
+ compression=None,
353
+ wait_for_ready=None,
354
+ timeout=None,
355
+ metadata=None):
356
+ return grpc.experimental.unary_unary(request, target, '/flwr.proto.Fleet/DeactivateNode',
357
+ flwr_dot_proto_dot_fleet__pb2.DeactivateNodeRequest.SerializeToString,
358
+ flwr_dot_proto_dot_fleet__pb2.DeactivateNodeResponse.FromString,
359
+ options, channel_credentials,
360
+ insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
361
+
362
+ @staticmethod
363
+ def UnregisterNode(request,
364
+ target,
365
+ options=(),
366
+ channel_credentials=None,
367
+ call_credentials=None,
368
+ insecure=False,
369
+ compression=None,
370
+ wait_for_ready=None,
371
+ timeout=None,
372
+ metadata=None):
373
+ return grpc.experimental.unary_unary(request, target, '/flwr.proto.Fleet/UnregisterNode',
374
+ flwr_dot_proto_dot_fleet__pb2.UnregisterNodeFleetRequest.SerializeToString,
375
+ flwr_dot_proto_dot_fleet__pb2.UnregisterNodeFleetResponse.FromString,
376
+ options, channel_credentials,
377
+ insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
378
+
243
379
  @staticmethod
244
380
  def SendNodeHeartbeat(request,
245
381
  target,
@@ -20,6 +20,26 @@ class FleetStub:
20
20
  flwr.proto.fleet_pb2.DeleteNodeRequest,
21
21
  flwr.proto.fleet_pb2.DeleteNodeResponse]
22
22
 
23
+ RegisterNode: grpc.UnaryUnaryMultiCallable[
24
+ flwr.proto.fleet_pb2.RegisterNodeFleetRequest,
25
+ flwr.proto.fleet_pb2.RegisterNodeFleetResponse]
26
+ """Register Node"""
27
+
28
+ ActivateNode: grpc.UnaryUnaryMultiCallable[
29
+ flwr.proto.fleet_pb2.ActivateNodeRequest,
30
+ flwr.proto.fleet_pb2.ActivateNodeResponse]
31
+ """Activate Node"""
32
+
33
+ DeactivateNode: grpc.UnaryUnaryMultiCallable[
34
+ flwr.proto.fleet_pb2.DeactivateNodeRequest,
35
+ flwr.proto.fleet_pb2.DeactivateNodeResponse]
36
+ """Deactivate Node"""
37
+
38
+ UnregisterNode: grpc.UnaryUnaryMultiCallable[
39
+ flwr.proto.fleet_pb2.UnregisterNodeFleetRequest,
40
+ flwr.proto.fleet_pb2.UnregisterNodeFleetResponse]
41
+ """Unregister Node"""
42
+
23
43
  SendNodeHeartbeat: grpc.UnaryUnaryMultiCallable[
24
44
  flwr.proto.heartbeat_pb2.SendNodeHeartbeatRequest,
25
45
  flwr.proto.heartbeat_pb2.SendNodeHeartbeatResponse]
@@ -78,6 +98,38 @@ class FleetServicer(metaclass=abc.ABCMeta):
78
98
  context: grpc.ServicerContext,
79
99
  ) -> flwr.proto.fleet_pb2.DeleteNodeResponse: ...
80
100
 
101
+ @abc.abstractmethod
102
+ def RegisterNode(self,
103
+ request: flwr.proto.fleet_pb2.RegisterNodeFleetRequest,
104
+ context: grpc.ServicerContext,
105
+ ) -> flwr.proto.fleet_pb2.RegisterNodeFleetResponse:
106
+ """Register Node"""
107
+ pass
108
+
109
+ @abc.abstractmethod
110
+ def ActivateNode(self,
111
+ request: flwr.proto.fleet_pb2.ActivateNodeRequest,
112
+ context: grpc.ServicerContext,
113
+ ) -> flwr.proto.fleet_pb2.ActivateNodeResponse:
114
+ """Activate Node"""
115
+ pass
116
+
117
+ @abc.abstractmethod
118
+ def DeactivateNode(self,
119
+ request: flwr.proto.fleet_pb2.DeactivateNodeRequest,
120
+ context: grpc.ServicerContext,
121
+ ) -> flwr.proto.fleet_pb2.DeactivateNodeResponse:
122
+ """Deactivate Node"""
123
+ pass
124
+
125
+ @abc.abstractmethod
126
+ def UnregisterNode(self,
127
+ request: flwr.proto.fleet_pb2.UnregisterNodeFleetRequest,
128
+ context: grpc.ServicerContext,
129
+ ) -> flwr.proto.fleet_pb2.UnregisterNodeFleetResponse:
130
+ """Unregister Node"""
131
+ pass
132
+
81
133
  @abc.abstractmethod
82
134
  def SendNodeHeartbeat(self,
83
135
  request: flwr.proto.heartbeat_pb2.SendNodeHeartbeatRequest,
@@ -33,10 +33,14 @@ from flwr.common.version import package_name, package_version
33
33
  from flwr.proto import grpcadapter_pb2_grpc # pylint: disable=E0611
34
34
  from flwr.proto.fab_pb2 import GetFabRequest # pylint: disable=E0611
35
35
  from flwr.proto.fleet_pb2 import ( # pylint: disable=E0611
36
+ ActivateNodeRequest,
36
37
  CreateNodeRequest,
38
+ DeactivateNodeRequest,
37
39
  DeleteNodeRequest,
38
40
  PullMessagesRequest,
39
41
  PushMessagesRequest,
42
+ RegisterNodeFleetRequest,
43
+ UnregisterNodeFleetRequest,
40
44
  )
41
45
  from flwr.proto.grpcadapter_pb2 import MessageContainer # pylint: disable=E0611
42
46
  from flwr.proto.heartbeat_pb2 import SendNodeHeartbeatRequest # pylint: disable=E0611
@@ -77,7 +81,7 @@ def _handle(
77
81
  class GrpcAdapterServicer(grpcadapter_pb2_grpc.GrpcAdapterServicer, FleetServicer):
78
82
  """Fleet API via GrpcAdapter servicer."""
79
83
 
80
- def SendReceive( # pylint: disable=too-many-return-statements
84
+ def SendReceive( # pylint: disable=too-many-return-statements, too-many-branches
81
85
  self, request: MessageContainer, context: grpc.ServicerContext
82
86
  ) -> MessageContainer:
83
87
  """."""
@@ -86,6 +90,18 @@ class GrpcAdapterServicer(grpcadapter_pb2_grpc.GrpcAdapterServicer, FleetService
86
90
  return _handle(request, context, CreateNodeRequest, self.CreateNode)
87
91
  if request.grpc_message_name == DeleteNodeRequest.__qualname__:
88
92
  return _handle(request, context, DeleteNodeRequest, self.DeleteNode)
93
+ if request.grpc_message_name == RegisterNodeFleetRequest.__qualname__:
94
+ return _handle(
95
+ request, context, RegisterNodeFleetRequest, self.RegisterNode
96
+ )
97
+ if request.grpc_message_name == ActivateNodeRequest.__qualname__:
98
+ return _handle(request, context, ActivateNodeRequest, self.ActivateNode)
99
+ if request.grpc_message_name == DeactivateNodeRequest.__qualname__:
100
+ return _handle(request, context, DeactivateNodeRequest, self.DeactivateNode)
101
+ if request.grpc_message_name == UnregisterNodeFleetRequest.__qualname__:
102
+ return _handle(
103
+ request, context, UnregisterNodeFleetRequest, self.UnregisterNode
104
+ )
89
105
  if request.grpc_message_name == SendNodeHeartbeatRequest.__qualname__:
90
106
  return _handle(
91
107
  request, context, SendNodeHeartbeatRequest, self.SendNodeHeartbeat
@@ -28,14 +28,22 @@ from flwr.common.typing import InvalidRunStatusException
28
28
  from flwr.proto import fleet_pb2_grpc # pylint: disable=E0611
29
29
  from flwr.proto.fab_pb2 import GetFabRequest, GetFabResponse # pylint: disable=E0611
30
30
  from flwr.proto.fleet_pb2 import ( # pylint: disable=E0611
31
+ ActivateNodeRequest,
32
+ ActivateNodeResponse,
31
33
  CreateNodeRequest,
32
34
  CreateNodeResponse,
35
+ DeactivateNodeRequest,
36
+ DeactivateNodeResponse,
33
37
  DeleteNodeRequest,
34
38
  DeleteNodeResponse,
35
39
  PullMessagesRequest,
36
40
  PullMessagesResponse,
37
41
  PushMessagesRequest,
38
42
  PushMessagesResponse,
43
+ RegisterNodeFleetRequest,
44
+ RegisterNodeFleetResponse,
45
+ UnregisterNodeFleetRequest,
46
+ UnregisterNodeFleetResponse,
39
47
  )
40
48
  from flwr.proto.heartbeat_pb2 import ( # pylint: disable=E0611
41
49
  SendNodeHeartbeatRequest,
@@ -140,6 +148,50 @@ class FleetServicer(fleet_pb2_grpc.FleetServicer):
140
148
  log(DEBUG, "[Fleet.CreateNode] Response: %s", MessageToDict(response))
141
149
  return response
142
150
 
151
+ def RegisterNode(
152
+ self, request: RegisterNodeFleetRequest, context: grpc.ServicerContext
153
+ ) -> RegisterNodeFleetResponse:
154
+ """Register a node (not implemented)."""
155
+ log(ERROR, "[Fleet.RegisterNode] RegisterNode is not implemented")
156
+ context.abort(
157
+ grpc.StatusCode.UNIMPLEMENTED,
158
+ "RegisterNode RPC is not yet implemented",
159
+ )
160
+ raise NotImplementedError
161
+
162
+ def ActivateNode(
163
+ self, request: ActivateNodeRequest, context: grpc.ServicerContext
164
+ ) -> ActivateNodeResponse:
165
+ """Activate a node (not implemented)."""
166
+ log(ERROR, "[Fleet.ActivateNode] ActivateNode is not implemented")
167
+ context.abort(
168
+ grpc.StatusCode.UNIMPLEMENTED,
169
+ "ActivateNode RPC is not yet implemented",
170
+ )
171
+ raise NotImplementedError
172
+
173
+ def DeactivateNode(
174
+ self, request: DeactivateNodeRequest, context: grpc.ServicerContext
175
+ ) -> DeactivateNodeResponse:
176
+ """Deactivate a node (not implemented)."""
177
+ log(ERROR, "[Fleet.DeactivateNode] DeactivateNode is not implemented")
178
+ context.abort(
179
+ grpc.StatusCode.UNIMPLEMENTED,
180
+ "DeactivateNode RPC is not yet implemented",
181
+ )
182
+ raise NotImplementedError
183
+
184
+ def UnregisterNode(
185
+ self, request: UnregisterNodeFleetRequest, context: grpc.ServicerContext
186
+ ) -> UnregisterNodeFleetResponse:
187
+ """Unregister a node (not implemented)."""
188
+ log(ERROR, "[Fleet.UnregisterNode] UnregisterNode is not implemented")
189
+ context.abort(
190
+ grpc.StatusCode.UNIMPLEMENTED,
191
+ "UnregisterNode RPC is not yet implemented",
192
+ )
193
+ raise NotImplementedError
194
+
143
195
  def DeleteNode(
144
196
  self, request: DeleteNodeRequest, context: grpc.ServicerContext
145
197
  ) -> DeleteNodeResponse:
@@ -25,14 +25,22 @@ from google.protobuf.message import Message as GrpcMessage
25
25
  from flwr.common.exit import ExitCode, flwr_exit
26
26
  from flwr.proto.fab_pb2 import GetFabRequest, GetFabResponse # pylint: disable=E0611
27
27
  from flwr.proto.fleet_pb2 import ( # pylint: disable=E0611
28
+ ActivateNodeRequest,
29
+ ActivateNodeResponse,
28
30
  CreateNodeRequest,
29
31
  CreateNodeResponse,
32
+ DeactivateNodeRequest,
33
+ DeactivateNodeResponse,
30
34
  DeleteNodeRequest,
31
35
  DeleteNodeResponse,
32
36
  PullMessagesRequest,
33
37
  PullMessagesResponse,
34
38
  PushMessagesRequest,
35
39
  PushMessagesResponse,
40
+ RegisterNodeFleetRequest,
41
+ RegisterNodeFleetResponse,
42
+ UnregisterNodeFleetRequest,
43
+ UnregisterNodeFleetResponse,
36
44
  )
37
45
  from flwr.proto.heartbeat_pb2 import ( # pylint: disable=E0611
38
46
  SendNodeHeartbeatRequest,
@@ -69,6 +77,8 @@ GrpcResponse = TypeVar("GrpcResponse", bound=GrpcMessage)
69
77
  GrpcAsyncFunction = Callable[[GrpcRequest], Awaitable[GrpcResponse]]
70
78
  RestEndPoint = Callable[[Request], Awaitable[Response]]
71
79
 
80
+ routes = []
81
+
72
82
 
73
83
  def rest_request_response(
74
84
  grpc_request_type: type[GrpcRequest],
@@ -76,6 +86,7 @@ def rest_request_response(
76
86
  """Convert an async gRPC-based function into a RESTful HTTP endpoint."""
77
87
 
78
88
  def decorator(func: GrpcAsyncFunction[GrpcRequest, GrpcResponse]) -> RestEndPoint:
89
+
79
90
  async def wrapper(request: Request) -> Response:
80
91
  _check_headers(request.headers)
81
92
 
@@ -91,6 +102,9 @@ def rest_request_response(
91
102
  headers={"Content-Type": "application/protobuf"},
92
103
  )
93
104
 
105
+ # Register route
106
+ path = f"/api/v0/fleet/{func.__name__.replace('_', '-')}"
107
+ routes.append(Route(path, wrapper, methods=["POST"]))
94
108
  return wrapper
95
109
 
96
110
  return decorator
@@ -116,8 +130,40 @@ async def delete_node(request: DeleteNodeRequest) -> DeleteNodeResponse:
116
130
  return message_handler.delete_node(request=request, state=state)
117
131
 
118
132
 
133
+ @rest_request_response(RegisterNodeFleetRequest)
134
+ async def register_node(
135
+ request: RegisterNodeFleetRequest,
136
+ ) -> RegisterNodeFleetResponse:
137
+ """Register a node (Fleet API only)."""
138
+ raise NotImplementedError("RegisterNode is not yet implemented.")
139
+
140
+
141
+ @rest_request_response(ActivateNodeRequest)
142
+ async def activate_node(
143
+ request: ActivateNodeRequest,
144
+ ) -> ActivateNodeResponse:
145
+ """Activate a node."""
146
+ raise NotImplementedError("ActivateNode is not yet implemented.")
147
+
148
+
149
+ @rest_request_response(DeactivateNodeRequest)
150
+ async def deactivate_node(
151
+ request: DeactivateNodeRequest,
152
+ ) -> DeactivateNodeResponse:
153
+ """Deactivate a node."""
154
+ raise NotImplementedError("DeactivateNode is not yet implemented.")
155
+
156
+
157
+ @rest_request_response(UnregisterNodeFleetRequest)
158
+ async def unregister_node(
159
+ request: UnregisterNodeFleetRequest,
160
+ ) -> UnregisterNodeFleetResponse:
161
+ """Unregister a node (Fleet API only)."""
162
+ raise NotImplementedError("UnregisterNode is not yet implemented.")
163
+
164
+
119
165
  @rest_request_response(PullMessagesRequest)
120
- async def pull_message(request: PullMessagesRequest) -> PullMessagesResponse:
166
+ async def pull_messages(request: PullMessagesRequest) -> PullMessagesResponse:
121
167
  """Pull PullMessages."""
122
168
  # Get state from app
123
169
  state: LinkState = cast(LinkStateFactory, app.state.STATE_FACTORY).state()
@@ -128,7 +174,7 @@ async def pull_message(request: PullMessagesRequest) -> PullMessagesResponse:
128
174
 
129
175
 
130
176
  @rest_request_response(PushMessagesRequest)
131
- async def push_message(request: PushMessagesRequest) -> PushMessagesResponse:
177
+ async def push_messages(request: PushMessagesRequest) -> PushMessagesResponse:
132
178
  """Pull PushMessages."""
133
179
  # Get state from app
134
180
  state: LinkState = cast(LinkStateFactory, app.state.STATE_FACTORY).state()
@@ -212,23 +258,6 @@ async def confirm_message_received(
212
258
  )
213
259
 
214
260
 
215
- routes = [
216
- Route("/api/v0/fleet/create-node", create_node, methods=["POST"]),
217
- Route("/api/v0/fleet/delete-node", delete_node, methods=["POST"]),
218
- Route("/api/v0/fleet/pull-messages", pull_message, methods=["POST"]),
219
- Route("/api/v0/fleet/push-messages", push_message, methods=["POST"]),
220
- Route("/api/v0/fleet/pull-object", pull_object, methods=["POST"]),
221
- Route("/api/v0/fleet/push-object", push_object, methods=["POST"]),
222
- Route("/api/v0/fleet/send-node-heartbeat", send_node_heartbeat, methods=["POST"]),
223
- Route("/api/v0/fleet/get-run", get_run, methods=["POST"]),
224
- Route("/api/v0/fleet/get-fab", get_fab, methods=["POST"]),
225
- Route(
226
- "/api/v0/fleet/confirm-message-received",
227
- confirm_message_received,
228
- methods=["POST"],
229
- ),
230
- ]
231
-
232
261
  app: Starlette = Starlette(
233
262
  debug=False,
234
263
  routes=routes,
@@ -386,6 +386,47 @@ class InMemoryLinkState(LinkState): # pylint: disable=R0902,R0904
386
386
  # Set online_until to current timestamp on deletion, if it is in the future
387
387
  node.online_until = min(node.online_until, current.timestamp())
388
388
 
389
+ def activate_node(self, node_id: int, heartbeat_interval: float) -> bool:
390
+ """Activate the node with the specified `node_id`."""
391
+ with self.lock:
392
+ self._check_and_tag_offline_nodes(node_ids=[node_id])
393
+
394
+ # Check if the node exists
395
+ if not (node := self.nodes.get(node_id)):
396
+ return False
397
+
398
+ # Only activate if the node is currently registered or offline
399
+ current_dt = now()
400
+ if node.status in (NodeStatus.REGISTERED, NodeStatus.OFFLINE):
401
+ node.status = NodeStatus.ONLINE
402
+ node.last_activated_at = current_dt.isoformat()
403
+ node.online_until = (
404
+ current_dt.timestamp() + HEARTBEAT_PATIENCE * heartbeat_interval
405
+ )
406
+ node.heartbeat_interval = heartbeat_interval
407
+ return True
408
+ return False
409
+
410
+ def deactivate_node(self, node_id: int) -> bool:
411
+ """Deactivate the node with the specified `node_id`."""
412
+ with self.lock:
413
+ self._check_and_tag_offline_nodes(node_ids=[node_id])
414
+
415
+ # Check if the node exists
416
+ if not (node := self.nodes.get(node_id)):
417
+ return False
418
+
419
+ # Only deactivate if the node is currently online
420
+ current_dt = now()
421
+ if node.status == NodeStatus.ONLINE:
422
+ node.status = NodeStatus.OFFLINE
423
+ node.last_deactivated_at = current_dt.isoformat()
424
+
425
+ # Set online_until to current timestamp
426
+ node.online_until = current_dt.timestamp()
427
+ return True
428
+ return False
429
+
389
430
  def get_nodes(self, run_id: int) -> set[int]:
390
431
  """Return all available nodes.
391
432
 
@@ -411,10 +452,10 @@ class InMemoryLinkState(LinkState): # pylint: disable=R0902,R0904
411
452
  ) -> Sequence[NodeInfo]:
412
453
  """Retrieve information about nodes based on the specified filters."""
413
454
  with self.lock:
414
- self._check_and_tag_deactivated_nodes()
455
+ self._check_and_tag_offline_nodes()
415
456
  result = []
416
- for node in self.nodes.values():
417
- if node_ids is not None and node.node_id not in node_ids:
457
+ for node_id in self.nodes.keys() if node_ids is None else node_ids:
458
+ if (node := self.nodes.get(node_id)) is None:
418
459
  continue
419
460
  if owner_aids is not None and node.owner_aid not in owner_aids:
420
461
  continue
@@ -423,11 +464,15 @@ class InMemoryLinkState(LinkState): # pylint: disable=R0902,R0904
423
464
  result.append(node)
424
465
  return result
425
466
 
426
- def _check_and_tag_deactivated_nodes(self) -> None:
467
+ def _check_and_tag_offline_nodes(
468
+ self, node_ids: Optional[list[int]] = None
469
+ ) -> None:
427
470
  with self.lock:
428
471
  # Set all nodes of "online" status to "offline" if they've offline
429
472
  current_ts = now().timestamp()
430
- for node in self.nodes.values():
473
+ for node_id in node_ids or self.nodes.keys():
474
+ if (node := self.nodes.get(node_id)) is None:
475
+ continue
431
476
  if node.status == NodeStatus.ONLINE:
432
477
  if node.online_until <= current_ts:
433
478
  node.status = NodeStatus.OFFLINE
@@ -139,6 +139,45 @@ class LinkState(CoreState): # pylint: disable=R0904
139
139
  def delete_node(self, owner_aid: str, node_id: int) -> None:
140
140
  """Remove `node_id` from the link state."""
141
141
 
142
+ @abc.abstractmethod
143
+ def activate_node(self, node_id: int, heartbeat_interval: float) -> bool:
144
+ """Activate the node with the specified `node_id`.
145
+
146
+ Transitions the node status to "online". The transition will fail
147
+ if the current status is not "registered" or "offline".
148
+
149
+ Parameters
150
+ ----------
151
+ node_id : int
152
+ The identifier of the node to activate.
153
+ heartbeat_interval : float
154
+ The interval (in seconds) from the current timestamp within which
155
+ the next heartbeat from this node is expected to be received.
156
+
157
+ Returns
158
+ -------
159
+ bool
160
+ True if the status transition was successful, False otherwise.
161
+ """
162
+
163
+ @abc.abstractmethod
164
+ def deactivate_node(self, node_id: int) -> bool:
165
+ """Deactivate the node with the specified `node_id`.
166
+
167
+ Transitions the node status to "offline". The transition will fail
168
+ if the current status is not "online".
169
+
170
+ Parameters
171
+ ----------
172
+ node_id : int
173
+ The identifier of the node to deactivate.
174
+
175
+ Returns
176
+ -------
177
+ bool
178
+ True if the status transition was successful, False otherwise.
179
+ """
180
+
142
181
  @abc.abstractmethod
143
182
  def get_nodes(self, run_id: int) -> set[int]:
144
183
  """Retrieve all currently stored node IDs as a set.