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.
- flwr/cli/build.py +118 -47
- flwr/cli/run/run.py +8 -4
- flwr/client/grpc_rere_client/grpc_adapter.py +32 -0
- flwr/common/constant.py +11 -1
- flwr/proto/fleet_pb2.py +31 -15
- flwr/proto/fleet_pb2.pyi +80 -0
- flwr/proto/fleet_pb2_grpc.py +136 -0
- flwr/proto/fleet_pb2_grpc.pyi +52 -0
- flwr/server/superlink/fleet/grpc_adapter/grpc_adapter_servicer.py +17 -1
- flwr/server/superlink/fleet/grpc_rere/fleet_servicer.py +52 -0
- flwr/server/superlink/fleet/rest_rere/rest_api.py +48 -19
- flwr/server/superlink/linkstate/in_memory_linkstate.py +50 -5
- flwr/server/superlink/linkstate/linkstate.py +39 -0
- flwr/server/superlink/linkstate/sqlite_linkstate.py +84 -19
- {flwr_nightly-1.23.0.dev20251027.dist-info → flwr_nightly-1.23.0.dev20251029.dist-info}/METADATA +1 -1
- {flwr_nightly-1.23.0.dev20251027.dist-info → flwr_nightly-1.23.0.dev20251029.dist-info}/RECORD +18 -18
- {flwr_nightly-1.23.0.dev20251027.dist-info → flwr_nightly-1.23.0.dev20251029.dist-info}/WHEEL +0 -0
- {flwr_nightly-1.23.0.dev20251027.dist-info → flwr_nightly-1.23.0.dev20251029.dist-info}/entry_points.txt +0 -0
flwr/proto/fleet_pb2_grpc.py
CHANGED
|
@@ -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,
|
flwr/proto/fleet_pb2_grpc.pyi
CHANGED
|
@@ -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
|
|
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
|
|
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.
|
|
455
|
+
self._check_and_tag_offline_nodes()
|
|
415
456
|
result = []
|
|
416
|
-
for
|
|
417
|
-
if
|
|
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
|
|
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
|
|
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.
|