flwr-nightly 1.23.0.dev20251017__py3-none-any.whl → 1.23.0.dev20251021__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/app.py +4 -4
- flwr/cli/ls.py +5 -5
- flwr/cli/supernode/__init__.py +4 -4
- flwr/cli/supernode/ls.py +19 -26
- flwr/cli/supernode/{create.py → register.py} +13 -12
- flwr/cli/supernode/{delete.py → unregister.py} +12 -10
- flwr/common/constant.py +12 -4
- flwr/common/exit/exit_code.py +5 -0
- flwr/common/inflatable_utils.py +10 -10
- flwr/common/record/array.py +3 -3
- flwr/proto/control_pb2.py +15 -15
- flwr/proto/control_pb2.pyi +12 -17
- flwr/proto/control_pb2_grpc.py +41 -41
- flwr/proto/control_pb2_grpc.pyi +24 -24
- flwr/proto/node_pb2.py +2 -2
- flwr/proto/node_pb2.pyi +10 -10
- flwr/server/app.py +11 -0
- flwr/server/superlink/fleet/grpc_rere/fleet_servicer.py +28 -2
- flwr/server/superlink/linkstate/in_memory_linkstate.py +18 -12
- flwr/server/superlink/linkstate/sqlite_linkstate.py +41 -118
- flwr/supercore/constant.py +2 -2
- flwr/supercore/sqlite_mixin.py +188 -0
- flwr/superlink/servicer/control/control_servicer.py +30 -93
- flwr/supernode/start_client_internal.py +14 -0
- {flwr_nightly-1.23.0.dev20251017.dist-info → flwr_nightly-1.23.0.dev20251021.dist-info}/METADATA +1 -1
- {flwr_nightly-1.23.0.dev20251017.dist-info → flwr_nightly-1.23.0.dev20251021.dist-info}/RECORD +28 -27
- {flwr_nightly-1.23.0.dev20251017.dist-info → flwr_nightly-1.23.0.dev20251021.dist-info}/WHEEL +0 -0
- {flwr_nightly-1.23.0.dev20251017.dist-info → flwr_nightly-1.23.0.dev20251021.dist-info}/entry_points.txt +0 -0
flwr/proto/control_pb2_grpc.py
CHANGED
|
@@ -49,20 +49,20 @@ class ControlStub(object):
|
|
|
49
49
|
request_serializer=flwr_dot_proto_dot_control__pb2.PullArtifactsRequest.SerializeToString,
|
|
50
50
|
response_deserializer=flwr_dot_proto_dot_control__pb2.PullArtifactsResponse.FromString,
|
|
51
51
|
)
|
|
52
|
-
self.
|
|
53
|
-
'/flwr.proto.Control/
|
|
54
|
-
request_serializer=flwr_dot_proto_dot_control__pb2.
|
|
55
|
-
response_deserializer=flwr_dot_proto_dot_control__pb2.
|
|
52
|
+
self.RegisterNode = channel.unary_unary(
|
|
53
|
+
'/flwr.proto.Control/RegisterNode',
|
|
54
|
+
request_serializer=flwr_dot_proto_dot_control__pb2.RegisterNodeRequest.SerializeToString,
|
|
55
|
+
response_deserializer=flwr_dot_proto_dot_control__pb2.RegisterNodeResponse.FromString,
|
|
56
56
|
)
|
|
57
|
-
self.
|
|
58
|
-
'/flwr.proto.Control/
|
|
59
|
-
request_serializer=flwr_dot_proto_dot_control__pb2.
|
|
60
|
-
response_deserializer=flwr_dot_proto_dot_control__pb2.
|
|
57
|
+
self.UnregisterNode = channel.unary_unary(
|
|
58
|
+
'/flwr.proto.Control/UnregisterNode',
|
|
59
|
+
request_serializer=flwr_dot_proto_dot_control__pb2.UnregisterNodeRequest.SerializeToString,
|
|
60
|
+
response_deserializer=flwr_dot_proto_dot_control__pb2.UnregisterNodeResponse.FromString,
|
|
61
61
|
)
|
|
62
|
-
self.
|
|
63
|
-
'/flwr.proto.Control/
|
|
64
|
-
request_serializer=flwr_dot_proto_dot_control__pb2.
|
|
65
|
-
response_deserializer=flwr_dot_proto_dot_control__pb2.
|
|
62
|
+
self.ListNodes = channel.unary_unary(
|
|
63
|
+
'/flwr.proto.Control/ListNodes',
|
|
64
|
+
request_serializer=flwr_dot_proto_dot_control__pb2.ListNodesRequest.SerializeToString,
|
|
65
|
+
response_deserializer=flwr_dot_proto_dot_control__pb2.ListNodesResponse.FromString,
|
|
66
66
|
)
|
|
67
67
|
|
|
68
68
|
|
|
@@ -118,21 +118,21 @@ class ControlServicer(object):
|
|
|
118
118
|
context.set_details('Method not implemented!')
|
|
119
119
|
raise NotImplementedError('Method not implemented!')
|
|
120
120
|
|
|
121
|
-
def
|
|
122
|
-
"""
|
|
121
|
+
def RegisterNode(self, request, context):
|
|
122
|
+
"""Register SuperNode
|
|
123
123
|
"""
|
|
124
124
|
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
|
125
125
|
context.set_details('Method not implemented!')
|
|
126
126
|
raise NotImplementedError('Method not implemented!')
|
|
127
127
|
|
|
128
|
-
def
|
|
129
|
-
"""
|
|
128
|
+
def UnregisterNode(self, request, context):
|
|
129
|
+
"""Unregister SuperNode
|
|
130
130
|
"""
|
|
131
131
|
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
|
132
132
|
context.set_details('Method not implemented!')
|
|
133
133
|
raise NotImplementedError('Method not implemented!')
|
|
134
134
|
|
|
135
|
-
def
|
|
135
|
+
def ListNodes(self, request, context):
|
|
136
136
|
"""List SuperNodes
|
|
137
137
|
"""
|
|
138
138
|
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
|
@@ -177,20 +177,20 @@ def add_ControlServicer_to_server(servicer, server):
|
|
|
177
177
|
request_deserializer=flwr_dot_proto_dot_control__pb2.PullArtifactsRequest.FromString,
|
|
178
178
|
response_serializer=flwr_dot_proto_dot_control__pb2.PullArtifactsResponse.SerializeToString,
|
|
179
179
|
),
|
|
180
|
-
'
|
|
181
|
-
servicer.
|
|
182
|
-
request_deserializer=flwr_dot_proto_dot_control__pb2.
|
|
183
|
-
response_serializer=flwr_dot_proto_dot_control__pb2.
|
|
180
|
+
'RegisterNode': grpc.unary_unary_rpc_method_handler(
|
|
181
|
+
servicer.RegisterNode,
|
|
182
|
+
request_deserializer=flwr_dot_proto_dot_control__pb2.RegisterNodeRequest.FromString,
|
|
183
|
+
response_serializer=flwr_dot_proto_dot_control__pb2.RegisterNodeResponse.SerializeToString,
|
|
184
184
|
),
|
|
185
|
-
'
|
|
186
|
-
servicer.
|
|
187
|
-
request_deserializer=flwr_dot_proto_dot_control__pb2.
|
|
188
|
-
response_serializer=flwr_dot_proto_dot_control__pb2.
|
|
185
|
+
'UnregisterNode': grpc.unary_unary_rpc_method_handler(
|
|
186
|
+
servicer.UnregisterNode,
|
|
187
|
+
request_deserializer=flwr_dot_proto_dot_control__pb2.UnregisterNodeRequest.FromString,
|
|
188
|
+
response_serializer=flwr_dot_proto_dot_control__pb2.UnregisterNodeResponse.SerializeToString,
|
|
189
189
|
),
|
|
190
|
-
'
|
|
191
|
-
servicer.
|
|
192
|
-
request_deserializer=flwr_dot_proto_dot_control__pb2.
|
|
193
|
-
response_serializer=flwr_dot_proto_dot_control__pb2.
|
|
190
|
+
'ListNodes': grpc.unary_unary_rpc_method_handler(
|
|
191
|
+
servicer.ListNodes,
|
|
192
|
+
request_deserializer=flwr_dot_proto_dot_control__pb2.ListNodesRequest.FromString,
|
|
193
|
+
response_serializer=flwr_dot_proto_dot_control__pb2.ListNodesResponse.SerializeToString,
|
|
194
194
|
),
|
|
195
195
|
}
|
|
196
196
|
generic_handler = grpc.method_handlers_generic_handler(
|
|
@@ -322,7 +322,7 @@ class Control(object):
|
|
|
322
322
|
insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
|
|
323
323
|
|
|
324
324
|
@staticmethod
|
|
325
|
-
def
|
|
325
|
+
def RegisterNode(request,
|
|
326
326
|
target,
|
|
327
327
|
options=(),
|
|
328
328
|
channel_credentials=None,
|
|
@@ -332,14 +332,14 @@ class Control(object):
|
|
|
332
332
|
wait_for_ready=None,
|
|
333
333
|
timeout=None,
|
|
334
334
|
metadata=None):
|
|
335
|
-
return grpc.experimental.unary_unary(request, target, '/flwr.proto.Control/
|
|
336
|
-
flwr_dot_proto_dot_control__pb2.
|
|
337
|
-
flwr_dot_proto_dot_control__pb2.
|
|
335
|
+
return grpc.experimental.unary_unary(request, target, '/flwr.proto.Control/RegisterNode',
|
|
336
|
+
flwr_dot_proto_dot_control__pb2.RegisterNodeRequest.SerializeToString,
|
|
337
|
+
flwr_dot_proto_dot_control__pb2.RegisterNodeResponse.FromString,
|
|
338
338
|
options, channel_credentials,
|
|
339
339
|
insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
|
|
340
340
|
|
|
341
341
|
@staticmethod
|
|
342
|
-
def
|
|
342
|
+
def UnregisterNode(request,
|
|
343
343
|
target,
|
|
344
344
|
options=(),
|
|
345
345
|
channel_credentials=None,
|
|
@@ -349,14 +349,14 @@ class Control(object):
|
|
|
349
349
|
wait_for_ready=None,
|
|
350
350
|
timeout=None,
|
|
351
351
|
metadata=None):
|
|
352
|
-
return grpc.experimental.unary_unary(request, target, '/flwr.proto.Control/
|
|
353
|
-
flwr_dot_proto_dot_control__pb2.
|
|
354
|
-
flwr_dot_proto_dot_control__pb2.
|
|
352
|
+
return grpc.experimental.unary_unary(request, target, '/flwr.proto.Control/UnregisterNode',
|
|
353
|
+
flwr_dot_proto_dot_control__pb2.UnregisterNodeRequest.SerializeToString,
|
|
354
|
+
flwr_dot_proto_dot_control__pb2.UnregisterNodeResponse.FromString,
|
|
355
355
|
options, channel_credentials,
|
|
356
356
|
insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
|
|
357
357
|
|
|
358
358
|
@staticmethod
|
|
359
|
-
def
|
|
359
|
+
def ListNodes(request,
|
|
360
360
|
target,
|
|
361
361
|
options=(),
|
|
362
362
|
channel_credentials=None,
|
|
@@ -366,8 +366,8 @@ class Control(object):
|
|
|
366
366
|
wait_for_ready=None,
|
|
367
367
|
timeout=None,
|
|
368
368
|
metadata=None):
|
|
369
|
-
return grpc.experimental.unary_unary(request, target, '/flwr.proto.Control/
|
|
370
|
-
flwr_dot_proto_dot_control__pb2.
|
|
371
|
-
flwr_dot_proto_dot_control__pb2.
|
|
369
|
+
return grpc.experimental.unary_unary(request, target, '/flwr.proto.Control/ListNodes',
|
|
370
|
+
flwr_dot_proto_dot_control__pb2.ListNodesRequest.SerializeToString,
|
|
371
|
+
flwr_dot_proto_dot_control__pb2.ListNodesResponse.FromString,
|
|
372
372
|
options, channel_credentials,
|
|
373
373
|
insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
|
flwr/proto/control_pb2_grpc.pyi
CHANGED
|
@@ -44,19 +44,19 @@ class ControlStub:
|
|
|
44
44
|
flwr.proto.control_pb2.PullArtifactsResponse]
|
|
45
45
|
"""Pull artifacts generated during a run (flwr pull)"""
|
|
46
46
|
|
|
47
|
-
|
|
48
|
-
flwr.proto.control_pb2.
|
|
49
|
-
flwr.proto.control_pb2.
|
|
50
|
-
"""
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
flwr.proto.control_pb2.
|
|
54
|
-
flwr.proto.control_pb2.
|
|
55
|
-
"""
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
flwr.proto.control_pb2.
|
|
59
|
-
flwr.proto.control_pb2.
|
|
47
|
+
RegisterNode: grpc.UnaryUnaryMultiCallable[
|
|
48
|
+
flwr.proto.control_pb2.RegisterNodeRequest,
|
|
49
|
+
flwr.proto.control_pb2.RegisterNodeResponse]
|
|
50
|
+
"""Register SuperNode"""
|
|
51
|
+
|
|
52
|
+
UnregisterNode: grpc.UnaryUnaryMultiCallable[
|
|
53
|
+
flwr.proto.control_pb2.UnregisterNodeRequest,
|
|
54
|
+
flwr.proto.control_pb2.UnregisterNodeResponse]
|
|
55
|
+
"""Unregister SuperNode"""
|
|
56
|
+
|
|
57
|
+
ListNodes: grpc.UnaryUnaryMultiCallable[
|
|
58
|
+
flwr.proto.control_pb2.ListNodesRequest,
|
|
59
|
+
flwr.proto.control_pb2.ListNodesResponse]
|
|
60
60
|
"""List SuperNodes"""
|
|
61
61
|
|
|
62
62
|
|
|
@@ -118,26 +118,26 @@ class ControlServicer(metaclass=abc.ABCMeta):
|
|
|
118
118
|
pass
|
|
119
119
|
|
|
120
120
|
@abc.abstractmethod
|
|
121
|
-
def
|
|
122
|
-
request: flwr.proto.control_pb2.
|
|
121
|
+
def RegisterNode(self,
|
|
122
|
+
request: flwr.proto.control_pb2.RegisterNodeRequest,
|
|
123
123
|
context: grpc.ServicerContext,
|
|
124
|
-
) -> flwr.proto.control_pb2.
|
|
125
|
-
"""
|
|
124
|
+
) -> flwr.proto.control_pb2.RegisterNodeResponse:
|
|
125
|
+
"""Register SuperNode"""
|
|
126
126
|
pass
|
|
127
127
|
|
|
128
128
|
@abc.abstractmethod
|
|
129
|
-
def
|
|
130
|
-
request: flwr.proto.control_pb2.
|
|
129
|
+
def UnregisterNode(self,
|
|
130
|
+
request: flwr.proto.control_pb2.UnregisterNodeRequest,
|
|
131
131
|
context: grpc.ServicerContext,
|
|
132
|
-
) -> flwr.proto.control_pb2.
|
|
133
|
-
"""
|
|
132
|
+
) -> flwr.proto.control_pb2.UnregisterNodeResponse:
|
|
133
|
+
"""Unregister SuperNode"""
|
|
134
134
|
pass
|
|
135
135
|
|
|
136
136
|
@abc.abstractmethod
|
|
137
|
-
def
|
|
138
|
-
request: flwr.proto.control_pb2.
|
|
137
|
+
def ListNodes(self,
|
|
138
|
+
request: flwr.proto.control_pb2.ListNodesRequest,
|
|
139
139
|
context: grpc.ServicerContext,
|
|
140
|
-
) -> flwr.proto.control_pb2.
|
|
140
|
+
) -> flwr.proto.control_pb2.ListNodesResponse:
|
|
141
141
|
"""List SuperNodes"""
|
|
142
142
|
pass
|
|
143
143
|
|
flwr/proto/node_pb2.py
CHANGED
|
@@ -14,7 +14,7 @@ _sym_db = _symbol_database.Default()
|
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
|
|
17
|
-
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x15\x66lwr/proto/node.proto\x12\nflwr.proto\"\x17\n\x04Node\x12\x0f\n\x07node_id\x18\x01 \x01(\x04\"\
|
|
17
|
+
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x15\x66lwr/proto/node.proto\x12\nflwr.proto\"\x17\n\x04Node\x12\x0f\n\x07node_id\x18\x01 \x01(\x04\"\xd3\x02\n\x08NodeInfo\x12\x0f\n\x07node_id\x18\x01 \x01(\x04\x12\x11\n\towner_aid\x18\x02 \x01(\t\x12\x0e\n\x06status\x18\x03 \x01(\t\x12\x15\n\rregistered_at\x18\x04 \x01(\t\x12\x1e\n\x11last_activated_at\x18\x05 \x01(\tH\x00\x88\x01\x01\x12 \n\x13last_deactivated_at\x18\x06 \x01(\tH\x01\x88\x01\x01\x12\x1c\n\x0funregistered_at\x18\x07 \x01(\tH\x02\x88\x01\x01\x12\x19\n\x0conline_until\x18\x08 \x01(\x01H\x03\x88\x01\x01\x12\x1a\n\x12heartbeat_interval\x18\t \x01(\x01\x12\x12\n\npublic_key\x18\n \x01(\x0c\x42\x14\n\x12_last_activated_atB\x16\n\x14_last_deactivated_atB\x12\n\x10_unregistered_atB\x0f\n\r_online_untilb\x06proto3')
|
|
18
18
|
|
|
19
19
|
_globals = globals()
|
|
20
20
|
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
|
@@ -24,5 +24,5 @@ if _descriptor._USE_C_DESCRIPTORS == False:
|
|
|
24
24
|
_globals['_NODE']._serialized_start=37
|
|
25
25
|
_globals['_NODE']._serialized_end=60
|
|
26
26
|
_globals['_NODEINFO']._serialized_start=63
|
|
27
|
-
_globals['_NODEINFO']._serialized_end=
|
|
27
|
+
_globals['_NODEINFO']._serialized_end=402
|
|
28
28
|
# @@protoc_insertion_point(module_scope)
|
flwr/proto/node_pb2.pyi
CHANGED
|
@@ -26,20 +26,20 @@ class NodeInfo(google.protobuf.message.Message):
|
|
|
26
26
|
NODE_ID_FIELD_NUMBER: builtins.int
|
|
27
27
|
OWNER_AID_FIELD_NUMBER: builtins.int
|
|
28
28
|
STATUS_FIELD_NUMBER: builtins.int
|
|
29
|
-
|
|
29
|
+
REGISTERED_AT_FIELD_NUMBER: builtins.int
|
|
30
30
|
LAST_ACTIVATED_AT_FIELD_NUMBER: builtins.int
|
|
31
31
|
LAST_DEACTIVATED_AT_FIELD_NUMBER: builtins.int
|
|
32
|
-
|
|
32
|
+
UNREGISTERED_AT_FIELD_NUMBER: builtins.int
|
|
33
33
|
ONLINE_UNTIL_FIELD_NUMBER: builtins.int
|
|
34
34
|
HEARTBEAT_INTERVAL_FIELD_NUMBER: builtins.int
|
|
35
35
|
PUBLIC_KEY_FIELD_NUMBER: builtins.int
|
|
36
36
|
node_id: builtins.int
|
|
37
37
|
owner_aid: typing.Text
|
|
38
38
|
status: typing.Text
|
|
39
|
-
|
|
39
|
+
registered_at: typing.Text
|
|
40
40
|
last_activated_at: typing.Text
|
|
41
41
|
last_deactivated_at: typing.Text
|
|
42
|
-
|
|
42
|
+
unregistered_at: typing.Text
|
|
43
43
|
online_until: builtins.float
|
|
44
44
|
heartbeat_interval: builtins.float
|
|
45
45
|
public_key: builtins.bytes
|
|
@@ -48,22 +48,22 @@ class NodeInfo(google.protobuf.message.Message):
|
|
|
48
48
|
node_id: builtins.int = ...,
|
|
49
49
|
owner_aid: typing.Text = ...,
|
|
50
50
|
status: typing.Text = ...,
|
|
51
|
-
|
|
51
|
+
registered_at: typing.Text = ...,
|
|
52
52
|
last_activated_at: typing.Optional[typing.Text] = ...,
|
|
53
53
|
last_deactivated_at: typing.Optional[typing.Text] = ...,
|
|
54
|
-
|
|
54
|
+
unregistered_at: typing.Optional[typing.Text] = ...,
|
|
55
55
|
online_until: typing.Optional[builtins.float] = ...,
|
|
56
56
|
heartbeat_interval: builtins.float = ...,
|
|
57
57
|
public_key: builtins.bytes = ...,
|
|
58
58
|
) -> None: ...
|
|
59
|
-
def HasField(self, field_name: typing_extensions.Literal["
|
|
60
|
-
def ClearField(self, field_name: typing_extensions.Literal["
|
|
61
|
-
@typing.overload
|
|
62
|
-
def WhichOneof(self, oneof_group: typing_extensions.Literal["_deleted_at",b"_deleted_at"]) -> typing.Optional[typing_extensions.Literal["deleted_at"]]: ...
|
|
59
|
+
def HasField(self, field_name: typing_extensions.Literal["_last_activated_at",b"_last_activated_at","_last_deactivated_at",b"_last_deactivated_at","_online_until",b"_online_until","_unregistered_at",b"_unregistered_at","last_activated_at",b"last_activated_at","last_deactivated_at",b"last_deactivated_at","online_until",b"online_until","unregistered_at",b"unregistered_at"]) -> builtins.bool: ...
|
|
60
|
+
def ClearField(self, field_name: typing_extensions.Literal["_last_activated_at",b"_last_activated_at","_last_deactivated_at",b"_last_deactivated_at","_online_until",b"_online_until","_unregistered_at",b"_unregistered_at","heartbeat_interval",b"heartbeat_interval","last_activated_at",b"last_activated_at","last_deactivated_at",b"last_deactivated_at","node_id",b"node_id","online_until",b"online_until","owner_aid",b"owner_aid","public_key",b"public_key","registered_at",b"registered_at","status",b"status","unregistered_at",b"unregistered_at"]) -> None: ...
|
|
63
61
|
@typing.overload
|
|
64
62
|
def WhichOneof(self, oneof_group: typing_extensions.Literal["_last_activated_at",b"_last_activated_at"]) -> typing.Optional[typing_extensions.Literal["last_activated_at"]]: ...
|
|
65
63
|
@typing.overload
|
|
66
64
|
def WhichOneof(self, oneof_group: typing_extensions.Literal["_last_deactivated_at",b"_last_deactivated_at"]) -> typing.Optional[typing_extensions.Literal["last_deactivated_at"]]: ...
|
|
67
65
|
@typing.overload
|
|
68
66
|
def WhichOneof(self, oneof_group: typing_extensions.Literal["_online_until",b"_online_until"]) -> typing.Optional[typing_extensions.Literal["online_until"]]: ...
|
|
67
|
+
@typing.overload
|
|
68
|
+
def WhichOneof(self, oneof_group: typing_extensions.Literal["_unregistered_at",b"_unregistered_at"]) -> typing.Optional[typing_extensions.Literal["unregistered_at"]]: ...
|
|
69
69
|
global___NodeInfo = NodeInfo
|
flwr/server/app.py
CHANGED
|
@@ -235,6 +235,17 @@ def run_superlink() -> None:
|
|
|
235
235
|
|
|
236
236
|
# If supernode authentication is disabled, warn users
|
|
237
237
|
enable_supernode_auth: bool = args.enable_supernode_auth
|
|
238
|
+
if enable_supernode_auth and args.insecure:
|
|
239
|
+
url_v = f"https://flower.ai/docs/framework/v{package_version}/en/"
|
|
240
|
+
page = "how-to-authenticate-supernodes.html"
|
|
241
|
+
flwr_exit(
|
|
242
|
+
ExitCode.SUPERLINK_INVALID_ARGS,
|
|
243
|
+
"The `--enable-supernode-auth` flag requires encrypted TLS communications. "
|
|
244
|
+
"Please provide TLS certificates using the `--ssl-certfile`, "
|
|
245
|
+
"`--ssl-keyfile` and `--ssl-ca-certfile` arguments to your SuperLink. "
|
|
246
|
+
"Please refer to the Flower documentation for more information: "
|
|
247
|
+
f"{url_v}{page}",
|
|
248
|
+
)
|
|
238
249
|
if not enable_supernode_auth:
|
|
239
250
|
log(
|
|
240
251
|
WARN,
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
"""Fleet API gRPC request-response servicer."""
|
|
16
16
|
|
|
17
17
|
|
|
18
|
+
import threading
|
|
18
19
|
from logging import DEBUG, ERROR, INFO
|
|
19
20
|
|
|
20
21
|
import grpc
|
|
@@ -53,6 +54,7 @@ from flwr.proto.run_pb2 import GetRunRequest, GetRunResponse # pylint: disable=
|
|
|
53
54
|
from flwr.server.superlink.fleet.message_handler import message_handler
|
|
54
55
|
from flwr.server.superlink.linkstate import LinkStateFactory
|
|
55
56
|
from flwr.server.superlink.utils import abort_grpc_context
|
|
57
|
+
from flwr.supercore.constant import NodeStatus
|
|
56
58
|
from flwr.supercore.ffs import FfsFactory
|
|
57
59
|
from flwr.supercore.object_store import ObjectStoreFactory
|
|
58
60
|
|
|
@@ -71,6 +73,7 @@ class FleetServicer(fleet_pb2_grpc.FleetServicer):
|
|
|
71
73
|
self.ffs_factory = ffs_factory
|
|
72
74
|
self.objectstore_factory = objectstore_factory
|
|
73
75
|
self.enable_supernode_auth = enable_supernode_auth
|
|
76
|
+
self.lock = threading.Lock()
|
|
74
77
|
|
|
75
78
|
def CreateNode(
|
|
76
79
|
self, request: CreateNodeRequest, context: grpc.ServicerContext
|
|
@@ -88,8 +91,31 @@ class FleetServicer(fleet_pb2_grpc.FleetServicer):
|
|
|
88
91
|
|
|
89
92
|
# Check if public key is already in use
|
|
90
93
|
if node_id := state.get_node_id_by_public_key(request.public_key):
|
|
91
|
-
|
|
92
|
-
|
|
94
|
+
|
|
95
|
+
# Ensure only one request that requires checking the node state
|
|
96
|
+
# is processed at a time. This avoids race conditions when two
|
|
97
|
+
# SuperNodes try to connect at the same time with the same
|
|
98
|
+
# public key.
|
|
99
|
+
with self.lock:
|
|
100
|
+
node_info = state.get_node_info(node_ids=[node_id])[0]
|
|
101
|
+
if node_info.status == NodeStatus.ONLINE:
|
|
102
|
+
# Node is already active
|
|
103
|
+
log(
|
|
104
|
+
ERROR,
|
|
105
|
+
"Public key already in use (node_id=%s)",
|
|
106
|
+
node_id,
|
|
107
|
+
)
|
|
108
|
+
raise ValueError(
|
|
109
|
+
"Public key already in use by an active SuperNode"
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
# Prepare response with existing node_id
|
|
113
|
+
response = CreateNodeResponse(node=Node(node_id=node_id))
|
|
114
|
+
# Awknowledge heartbeat to mark node as online
|
|
115
|
+
state.acknowledge_node_heartbeat(
|
|
116
|
+
node_id=node_id,
|
|
117
|
+
heartbeat_interval=request.heartbeat_interval,
|
|
118
|
+
)
|
|
93
119
|
else:
|
|
94
120
|
if self.enable_supernode_auth:
|
|
95
121
|
# When SuperNode authentication is enabled,
|
|
@@ -262,6 +262,7 @@ class InMemoryLinkState(LinkState): # pylint: disable=R0902,R0904
|
|
|
262
262
|
node_id: self.nodes[node_id].online_until
|
|
263
263
|
for node_id in dst_node_ids
|
|
264
264
|
if node_id in self.nodes
|
|
265
|
+
and self.nodes[node_id].status != NodeStatus.UNREGISTERED
|
|
265
266
|
},
|
|
266
267
|
current_time=current,
|
|
267
268
|
)
|
|
@@ -353,11 +354,11 @@ class InMemoryLinkState(LinkState): # pylint: disable=R0902,R0904
|
|
|
353
354
|
self.nodes[node_id] = NodeInfo(
|
|
354
355
|
node_id=node_id,
|
|
355
356
|
owner_aid=owner_aid,
|
|
356
|
-
status=NodeStatus.
|
|
357
|
-
|
|
357
|
+
status=NodeStatus.REGISTERED,
|
|
358
|
+
registered_at=now().isoformat(),
|
|
358
359
|
last_activated_at=None,
|
|
359
360
|
last_deactivated_at=None,
|
|
360
|
-
|
|
361
|
+
unregistered_at=None,
|
|
361
362
|
online_until=None,
|
|
362
363
|
heartbeat_interval=heartbeat_interval,
|
|
363
364
|
public_key=public_key,
|
|
@@ -371,16 +372,19 @@ class InMemoryLinkState(LinkState): # pylint: disable=R0902,R0904
|
|
|
371
372
|
with self.lock:
|
|
372
373
|
if (
|
|
373
374
|
not (node := self.nodes.get(node_id))
|
|
374
|
-
or node.status == NodeStatus.
|
|
375
|
+
or node.status == NodeStatus.UNREGISTERED
|
|
375
376
|
or owner_aid != self.nodes[node_id].owner_aid
|
|
376
377
|
):
|
|
377
378
|
raise ValueError(
|
|
378
|
-
f"Node ID {node_id} already
|
|
379
|
-
"
|
|
379
|
+
f"Node ID {node_id} already unregistered, not found or "
|
|
380
|
+
"the request was unauthorized."
|
|
380
381
|
)
|
|
381
382
|
|
|
382
|
-
node.status = NodeStatus.
|
|
383
|
-
|
|
383
|
+
node.status = NodeStatus.UNREGISTERED
|
|
384
|
+
current = now()
|
|
385
|
+
node.unregistered_at = current.isoformat()
|
|
386
|
+
# Set online_until to current timestamp on deletion, if it is in the future
|
|
387
|
+
node.online_until = min(node.online_until, current.timestamp())
|
|
384
388
|
|
|
385
389
|
def get_nodes(self, run_id: int) -> set[int]:
|
|
386
390
|
"""Return all available nodes.
|
|
@@ -436,7 +440,7 @@ class InMemoryLinkState(LinkState): # pylint: disable=R0902,R0904
|
|
|
436
440
|
with self.lock:
|
|
437
441
|
if (
|
|
438
442
|
node := self.nodes.get(node_id)
|
|
439
|
-
) is None or node.status == NodeStatus.
|
|
443
|
+
) is None or node.status == NodeStatus.UNREGISTERED:
|
|
440
444
|
raise ValueError(f"Node ID {node_id} not found")
|
|
441
445
|
return node.public_key
|
|
442
446
|
|
|
@@ -450,7 +454,7 @@ class InMemoryLinkState(LinkState): # pylint: disable=R0902,R0904
|
|
|
450
454
|
return None
|
|
451
455
|
|
|
452
456
|
node_info = self.nodes[node_id]
|
|
453
|
-
if node_info.status == NodeStatus.
|
|
457
|
+
if node_info.status == NodeStatus.UNREGISTERED:
|
|
454
458
|
return None
|
|
455
459
|
return node_id
|
|
456
460
|
|
|
@@ -639,11 +643,13 @@ class InMemoryLinkState(LinkState): # pylint: disable=R0902,R0904
|
|
|
639
643
|
the node is marked as offline.
|
|
640
644
|
"""
|
|
641
645
|
with self.lock:
|
|
642
|
-
if (
|
|
646
|
+
if (
|
|
647
|
+
node := self.nodes.get(node_id)
|
|
648
|
+
) and node.status != NodeStatus.UNREGISTERED:
|
|
643
649
|
current_dt = now()
|
|
644
650
|
|
|
645
651
|
# Set timestamp if the status changes
|
|
646
|
-
if node.status != NodeStatus.ONLINE: # offline or
|
|
652
|
+
if node.status != NodeStatus.ONLINE: # offline or registered
|
|
647
653
|
node.status = NodeStatus.ONLINE
|
|
648
654
|
node.last_activated_at = current_dt.isoformat()
|
|
649
655
|
|