flwr-nightly 1.23.0.dev20251017__py3-none-any.whl → 1.23.0.dev20251020__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/supernode/__init__.py +4 -4
- flwr/cli/supernode/ls.py +12 -12
- flwr/cli/supernode/{create.py → register.py} +13 -12
- flwr/cli/supernode/{delete.py → unregister.py} +12 -10
- flwr/common/exit/exit_code.py +5 -0
- flwr/proto/control_pb2.py +15 -15
- flwr/proto/control_pb2.pyi +12 -12
- 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 +14 -12
- flwr/server/superlink/linkstate/sqlite_linkstate.py +13 -13
- flwr/supercore/constant.py +2 -2
- flwr/superlink/servicer/control/control_servicer.py +41 -40
- flwr/supernode/start_client_internal.py +14 -0
- {flwr_nightly-1.23.0.dev20251017.dist-info → flwr_nightly-1.23.0.dev20251020.dist-info}/METADATA +1 -1
- {flwr_nightly-1.23.0.dev20251017.dist-info → flwr_nightly-1.23.0.dev20251020.dist-info}/RECORD +23 -23
- {flwr_nightly-1.23.0.dev20251017.dist-info → flwr_nightly-1.23.0.dev20251020.dist-info}/WHEEL +0 -0
- {flwr_nightly-1.23.0.dev20251017.dist-info → flwr_nightly-1.23.0.dev20251020.dist-info}/entry_points.txt +0 -0
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,
|
|
@@ -353,11 +353,11 @@ class InMemoryLinkState(LinkState): # pylint: disable=R0902,R0904
|
|
|
353
353
|
self.nodes[node_id] = NodeInfo(
|
|
354
354
|
node_id=node_id,
|
|
355
355
|
owner_aid=owner_aid,
|
|
356
|
-
status=NodeStatus.
|
|
357
|
-
|
|
356
|
+
status=NodeStatus.REGISTERED,
|
|
357
|
+
registered_at=now().isoformat(),
|
|
358
358
|
last_activated_at=None,
|
|
359
359
|
last_deactivated_at=None,
|
|
360
|
-
|
|
360
|
+
unregistered_at=None,
|
|
361
361
|
online_until=None,
|
|
362
362
|
heartbeat_interval=heartbeat_interval,
|
|
363
363
|
public_key=public_key,
|
|
@@ -371,16 +371,16 @@ class InMemoryLinkState(LinkState): # pylint: disable=R0902,R0904
|
|
|
371
371
|
with self.lock:
|
|
372
372
|
if (
|
|
373
373
|
not (node := self.nodes.get(node_id))
|
|
374
|
-
or node.status == NodeStatus.
|
|
374
|
+
or node.status == NodeStatus.UNREGISTERED
|
|
375
375
|
or owner_aid != self.nodes[node_id].owner_aid
|
|
376
376
|
):
|
|
377
377
|
raise ValueError(
|
|
378
|
-
f"Node ID {node_id} already
|
|
379
|
-
"
|
|
378
|
+
f"Node ID {node_id} already unregistered, not found or "
|
|
379
|
+
"the request was unauthorized."
|
|
380
380
|
)
|
|
381
381
|
|
|
382
|
-
node.status = NodeStatus.
|
|
383
|
-
node.
|
|
382
|
+
node.status = NodeStatus.UNREGISTERED
|
|
383
|
+
node.unregistered_at = now().isoformat()
|
|
384
384
|
|
|
385
385
|
def get_nodes(self, run_id: int) -> set[int]:
|
|
386
386
|
"""Return all available nodes.
|
|
@@ -436,7 +436,7 @@ class InMemoryLinkState(LinkState): # pylint: disable=R0902,R0904
|
|
|
436
436
|
with self.lock:
|
|
437
437
|
if (
|
|
438
438
|
node := self.nodes.get(node_id)
|
|
439
|
-
) is None or node.status == NodeStatus.
|
|
439
|
+
) is None or node.status == NodeStatus.UNREGISTERED:
|
|
440
440
|
raise ValueError(f"Node ID {node_id} not found")
|
|
441
441
|
return node.public_key
|
|
442
442
|
|
|
@@ -450,7 +450,7 @@ class InMemoryLinkState(LinkState): # pylint: disable=R0902,R0904
|
|
|
450
450
|
return None
|
|
451
451
|
|
|
452
452
|
node_info = self.nodes[node_id]
|
|
453
|
-
if node_info.status == NodeStatus.
|
|
453
|
+
if node_info.status == NodeStatus.UNREGISTERED:
|
|
454
454
|
return None
|
|
455
455
|
return node_id
|
|
456
456
|
|
|
@@ -639,11 +639,13 @@ class InMemoryLinkState(LinkState): # pylint: disable=R0902,R0904
|
|
|
639
639
|
the node is marked as offline.
|
|
640
640
|
"""
|
|
641
641
|
with self.lock:
|
|
642
|
-
if (
|
|
642
|
+
if (
|
|
643
|
+
node := self.nodes.get(node_id)
|
|
644
|
+
) and node.status != NodeStatus.UNREGISTERED:
|
|
643
645
|
current_dt = now()
|
|
644
646
|
|
|
645
647
|
# Set timestamp if the status changes
|
|
646
|
-
if node.status != NodeStatus.ONLINE: # offline or
|
|
648
|
+
if node.status != NodeStatus.ONLINE: # offline or registered
|
|
647
649
|
node.status = NodeStatus.ONLINE
|
|
648
650
|
node.last_activated_at = current_dt.isoformat()
|
|
649
651
|
|
|
@@ -76,10 +76,10 @@ CREATE TABLE IF NOT EXISTS node(
|
|
|
76
76
|
node_id INTEGER UNIQUE,
|
|
77
77
|
owner_aid TEXT,
|
|
78
78
|
status TEXT,
|
|
79
|
-
|
|
79
|
+
registered_at TEXT,
|
|
80
80
|
last_activated_at TEXT NULL,
|
|
81
81
|
last_deactivated_at TEXT NULL,
|
|
82
|
-
|
|
82
|
+
unregistered_at TEXT NULL,
|
|
83
83
|
online_until TIMESTAMP NULL,
|
|
84
84
|
heartbeat_interval REAL,
|
|
85
85
|
public_key BLOB UNIQUE
|
|
@@ -623,8 +623,8 @@ class SqliteLinkState(LinkState): # pylint: disable=R0904
|
|
|
623
623
|
|
|
624
624
|
query = """
|
|
625
625
|
INSERT INTO node
|
|
626
|
-
(node_id, owner_aid, status,
|
|
627
|
-
last_deactivated_at,
|
|
626
|
+
(node_id, owner_aid, status, registered_at, last_activated_at,
|
|
627
|
+
last_deactivated_at, unregistered_at, online_until, heartbeat_interval,
|
|
628
628
|
public_key)
|
|
629
629
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
630
630
|
"""
|
|
@@ -636,11 +636,11 @@ class SqliteLinkState(LinkState): # pylint: disable=R0904
|
|
|
636
636
|
(
|
|
637
637
|
sint64_node_id, # node_id
|
|
638
638
|
owner_aid, # owner_aid
|
|
639
|
-
NodeStatus.
|
|
640
|
-
now().isoformat(), #
|
|
639
|
+
NodeStatus.REGISTERED, # status
|
|
640
|
+
now().isoformat(), # registered_at
|
|
641
641
|
None, # last_activated_at
|
|
642
642
|
None, # last_deactivated_at
|
|
643
|
-
None, #
|
|
643
|
+
None, # unregistered_at
|
|
644
644
|
None, # online_until, initialized with offline status
|
|
645
645
|
heartbeat_interval, # heartbeat_interval
|
|
646
646
|
public_key, # public_key
|
|
@@ -662,15 +662,15 @@ class SqliteLinkState(LinkState): # pylint: disable=R0904
|
|
|
662
662
|
|
|
663
663
|
query = """
|
|
664
664
|
UPDATE node
|
|
665
|
-
SET status = ?,
|
|
665
|
+
SET status = ?, unregistered_at = ?
|
|
666
666
|
WHERE node_id = ? AND status != ? AND owner_aid = ?
|
|
667
667
|
RETURNING node_id
|
|
668
668
|
"""
|
|
669
669
|
params = (
|
|
670
|
-
NodeStatus.
|
|
670
|
+
NodeStatus.UNREGISTERED,
|
|
671
671
|
now().isoformat(),
|
|
672
672
|
sint64_node_id,
|
|
673
|
-
NodeStatus.
|
|
673
|
+
NodeStatus.UNREGISTERED,
|
|
674
674
|
owner_aid,
|
|
675
675
|
)
|
|
676
676
|
|
|
@@ -775,7 +775,7 @@ class SqliteLinkState(LinkState): # pylint: disable=R0904
|
|
|
775
775
|
|
|
776
776
|
# Query the public key for the given node_id
|
|
777
777
|
query = "SELECT public_key FROM node WHERE node_id = ? AND status != ?;"
|
|
778
|
-
rows = self.query(query, (sint64_node_id, NodeStatus.
|
|
778
|
+
rows = self.query(query, (sint64_node_id, NodeStatus.UNREGISTERED))
|
|
779
779
|
|
|
780
780
|
# If no result is found, return None
|
|
781
781
|
if not rows:
|
|
@@ -788,7 +788,7 @@ class SqliteLinkState(LinkState): # pylint: disable=R0904
|
|
|
788
788
|
"""Get `node_id` for the specified `public_key` if it exists and is not
|
|
789
789
|
deleted."""
|
|
790
790
|
query = "SELECT node_id FROM node WHERE public_key = ? AND status != ?;"
|
|
791
|
-
rows = self.query(query, (public_key, NodeStatus.
|
|
791
|
+
rows = self.query(query, (public_key, NodeStatus.UNREGISTERED))
|
|
792
792
|
|
|
793
793
|
# If no result is found, return None
|
|
794
794
|
if not rows:
|
|
@@ -1059,7 +1059,7 @@ class SqliteLinkState(LinkState): # pylint: disable=R0904
|
|
|
1059
1059
|
# Check if node exists and not deleted
|
|
1060
1060
|
query = "SELECT status FROM node WHERE node_id = ? AND status != ?"
|
|
1061
1061
|
row = self.conn.execute(
|
|
1062
|
-
query, (sint64_node_id, NodeStatus.
|
|
1062
|
+
query, (sint64_node_id, NodeStatus.UNREGISTERED)
|
|
1063
1063
|
).fetchone()
|
|
1064
1064
|
if row is None:
|
|
1065
1065
|
return False
|
flwr/supercore/constant.py
CHANGED
|
@@ -24,10 +24,10 @@ EXEC_PLUGIN_SECTION = "exec_plugin"
|
|
|
24
24
|
class NodeStatus:
|
|
25
25
|
"""Event log writer types."""
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
REGISTERED = "registered"
|
|
28
28
|
ONLINE = "online"
|
|
29
29
|
OFFLINE = "offline"
|
|
30
|
-
|
|
30
|
+
UNREGISTERED = "unregistered"
|
|
31
31
|
|
|
32
32
|
def __new__(cls) -> NodeStatus:
|
|
33
33
|
"""Prevent instantiation."""
|
|
@@ -49,29 +49,30 @@ from flwr.common.serde import (
|
|
|
49
49
|
from flwr.common.typing import Fab, Run, RunStatus
|
|
50
50
|
from flwr.proto import control_pb2_grpc # pylint: disable=E0611
|
|
51
51
|
from flwr.proto.control_pb2 import ( # pylint: disable=E0611
|
|
52
|
-
CreateNodeCliRequest,
|
|
53
|
-
CreateNodeCliResponse,
|
|
54
|
-
DeleteNodeCliRequest,
|
|
55
|
-
DeleteNodeCliResponse,
|
|
56
52
|
GetAuthTokensRequest,
|
|
57
53
|
GetAuthTokensResponse,
|
|
58
54
|
GetLoginDetailsRequest,
|
|
59
55
|
GetLoginDetailsResponse,
|
|
60
|
-
|
|
61
|
-
|
|
56
|
+
ListNodesRequest,
|
|
57
|
+
ListNodesResponse,
|
|
62
58
|
ListRunsRequest,
|
|
63
59
|
ListRunsResponse,
|
|
64
60
|
PullArtifactsRequest,
|
|
65
61
|
PullArtifactsResponse,
|
|
62
|
+
RegisterNodeRequest,
|
|
63
|
+
RegisterNodeResponse,
|
|
66
64
|
StartRunRequest,
|
|
67
65
|
StartRunResponse,
|
|
68
66
|
StopRunRequest,
|
|
69
67
|
StopRunResponse,
|
|
70
68
|
StreamLogsRequest,
|
|
71
69
|
StreamLogsResponse,
|
|
70
|
+
UnregisterNodeRequest,
|
|
71
|
+
UnregisterNodeResponse,
|
|
72
72
|
)
|
|
73
73
|
from flwr.proto.node_pb2 import NodeInfo # pylint: disable=E0611
|
|
74
74
|
from flwr.server.superlink.linkstate import LinkState, LinkStateFactory
|
|
75
|
+
from flwr.supercore.constant import NodeStatus
|
|
75
76
|
from flwr.supercore.ffs import FfsFactory
|
|
76
77
|
from flwr.supercore.object_store import ObjectStore, ObjectStoreFactory
|
|
77
78
|
from flwr.supercore.primitives.asymmetric import bytes_to_public_key, uses_nist_ec_curve
|
|
@@ -389,11 +390,11 @@ class ControlServicer(control_pb2_grpc.ControlServicer):
|
|
|
389
390
|
download_url = self.artifact_provider.get_url(run_id)
|
|
390
391
|
return PullArtifactsResponse(url=download_url)
|
|
391
392
|
|
|
392
|
-
def
|
|
393
|
-
self, request:
|
|
394
|
-
) ->
|
|
393
|
+
def RegisterNode(
|
|
394
|
+
self, request: RegisterNodeRequest, context: grpc.ServicerContext
|
|
395
|
+
) -> RegisterNodeResponse:
|
|
395
396
|
"""Add a SuperNode."""
|
|
396
|
-
log(INFO, "ControlServicer.
|
|
397
|
+
log(INFO, "ControlServicer.RegisterNode")
|
|
397
398
|
|
|
398
399
|
# Verify public key
|
|
399
400
|
try:
|
|
@@ -427,15 +428,15 @@ class ControlServicer(control_pb2_grpc.ControlServicer):
|
|
|
427
428
|
context.abort(
|
|
428
429
|
grpc.StatusCode.FAILED_PRECONDITION, PUBLIC_KEY_ALREADY_IN_USE_MESSAGE
|
|
429
430
|
)
|
|
430
|
-
log(INFO, "[ControlServicer.
|
|
431
|
+
log(INFO, "[ControlServicer.RegisterNode] Created node_id=%s", node_id)
|
|
431
432
|
|
|
432
|
-
return
|
|
433
|
+
return RegisterNodeResponse(node_id=node_id)
|
|
433
434
|
|
|
434
|
-
def
|
|
435
|
-
self, request:
|
|
436
|
-
) ->
|
|
435
|
+
def UnregisterNode(
|
|
436
|
+
self, request: UnregisterNodeRequest, context: grpc.ServicerContext
|
|
437
|
+
) -> UnregisterNodeResponse:
|
|
437
438
|
"""Remove a SuperNode."""
|
|
438
|
-
log(INFO, "ControlServicer.
|
|
439
|
+
log(INFO, "ControlServicer.UnregisterNode")
|
|
439
440
|
|
|
440
441
|
# Init link state
|
|
441
442
|
state = self.linkstate_factory.state()
|
|
@@ -448,19 +449,19 @@ class ControlServicer(control_pb2_grpc.ControlServicer):
|
|
|
448
449
|
log(ERROR, NODE_NOT_FOUND_MESSAGE)
|
|
449
450
|
context.abort(grpc.StatusCode.NOT_FOUND, NODE_NOT_FOUND_MESSAGE)
|
|
450
451
|
|
|
451
|
-
return
|
|
452
|
+
return UnregisterNodeResponse()
|
|
452
453
|
|
|
453
|
-
def
|
|
454
|
-
self, request:
|
|
455
|
-
) ->
|
|
454
|
+
def ListNodes(
|
|
455
|
+
self, request: ListNodesRequest, context: grpc.ServicerContext
|
|
456
|
+
) -> ListNodesResponse:
|
|
456
457
|
"""List all SuperNodes."""
|
|
457
|
-
log(INFO, "ControlServicer.
|
|
458
|
+
log(INFO, "ControlServicer.ListNodes")
|
|
458
459
|
|
|
459
460
|
if self.is_simulation:
|
|
460
|
-
log(ERROR, "
|
|
461
|
+
log(ERROR, "ListNodes is not available in simulation mode.")
|
|
461
462
|
context.abort(
|
|
462
463
|
grpc.StatusCode.UNIMPLEMENTED,
|
|
463
|
-
"
|
|
464
|
+
"ListNodesis not available in simulation mode.",
|
|
464
465
|
)
|
|
465
466
|
raise grpc.RpcError() # This line is unreachable
|
|
466
467
|
|
|
@@ -478,61 +479,61 @@ class ControlServicer(control_pb2_grpc.ControlServicer):
|
|
|
478
479
|
# Retrieve all nodes for the account
|
|
479
480
|
nodes_info = state.get_node_info(owner_aids=[flwr_aid])
|
|
480
481
|
|
|
481
|
-
return
|
|
482
|
+
return ListNodesResponse(nodes_info=nodes_info, now=now().isoformat())
|
|
482
483
|
|
|
483
484
|
|
|
484
485
|
def _create_list_nodeif_for_dry_run() -> Sequence[NodeInfo]:
|
|
485
486
|
"""Create a list of NodeInfo for dry run testing."""
|
|
486
487
|
nodes_info: list[NodeInfo] = []
|
|
487
|
-
# A node
|
|
488
|
+
# A node registered (but not connected)
|
|
488
489
|
nodes_info.append(
|
|
489
490
|
NodeInfo(
|
|
490
491
|
node_id=15390646978706312628,
|
|
491
492
|
owner_aid="owner_aid_1",
|
|
492
|
-
status=
|
|
493
|
-
|
|
493
|
+
status=NodeStatus.REGISTERED,
|
|
494
|
+
registered_at=(now()).isoformat(),
|
|
494
495
|
last_activated_at="",
|
|
495
496
|
last_deactivated_at="",
|
|
496
|
-
|
|
497
|
+
unregistered_at="",
|
|
497
498
|
)
|
|
498
499
|
)
|
|
499
500
|
|
|
500
|
-
# A node
|
|
501
|
+
# A node registered and connected
|
|
501
502
|
nodes_info.append(
|
|
502
503
|
NodeInfo(
|
|
503
504
|
node_id=2941141058168602545,
|
|
504
505
|
owner_aid="owner_aid_2",
|
|
505
|
-
status=
|
|
506
|
-
|
|
506
|
+
status=NodeStatus.ONLINE,
|
|
507
|
+
registered_at=(now()).isoformat(),
|
|
507
508
|
last_activated_at=(now() + timedelta(hours=0.5)).isoformat(),
|
|
508
509
|
last_deactivated_at="",
|
|
509
|
-
|
|
510
|
+
unregistered_at="",
|
|
510
511
|
)
|
|
511
512
|
)
|
|
512
513
|
|
|
513
|
-
# A node
|
|
514
|
+
# A node registered and unregistered (never connected)
|
|
514
515
|
nodes_info.append(
|
|
515
516
|
NodeInfo(
|
|
516
517
|
node_id=906971720890549292,
|
|
517
518
|
owner_aid="owner_aid_3",
|
|
518
|
-
status=
|
|
519
|
-
|
|
519
|
+
status=NodeStatus.UNREGISTERED,
|
|
520
|
+
registered_at=(now()).isoformat(),
|
|
520
521
|
last_activated_at="",
|
|
521
522
|
last_deactivated_at="",
|
|
522
|
-
|
|
523
|
+
unregistered_at=(now() + timedelta(hours=1)).isoformat(),
|
|
523
524
|
)
|
|
524
525
|
)
|
|
525
526
|
|
|
526
|
-
# A node
|
|
527
|
+
# A node registered, deactivate and then unregistered
|
|
527
528
|
nodes_info.append(
|
|
528
529
|
NodeInfo(
|
|
529
530
|
node_id=1781174086018058152,
|
|
530
531
|
owner_aid="owner_aid_4",
|
|
531
|
-
status=
|
|
532
|
-
|
|
532
|
+
status=NodeStatus.OFFLINE,
|
|
533
|
+
registered_at=(now()).isoformat(),
|
|
533
534
|
last_activated_at=(now() + timedelta(hours=0.5)).isoformat(),
|
|
534
535
|
last_deactivated_at=(now() + timedelta(hours=1)).isoformat(),
|
|
535
|
-
|
|
536
|
+
unregistered_at=(now() + timedelta(hours=1.5)).isoformat(),
|
|
536
537
|
)
|
|
537
538
|
)
|
|
538
539
|
return nodes_info
|
|
@@ -54,6 +54,7 @@ from flwr.common.logger import log
|
|
|
54
54
|
from flwr.common.retry_invoker import RetryInvoker, _make_simple_grpc_retry_invoker
|
|
55
55
|
from flwr.common.telemetry import EventType
|
|
56
56
|
from flwr.common.typing import Fab, Run, RunNotRunningException, UserConfig
|
|
57
|
+
from flwr.common.version import package_version
|
|
57
58
|
from flwr.proto.clientappio_pb2_grpc import add_ClientAppIoServicer_to_server
|
|
58
59
|
from flwr.proto.message_pb2 import ObjectTree # pylint: disable=E0611
|
|
59
60
|
from flwr.supercore.ffs import Ffs, FfsFactory
|
|
@@ -141,6 +142,19 @@ def start_client_internal(
|
|
|
141
142
|
if insecure is None:
|
|
142
143
|
insecure = root_certificates is None
|
|
143
144
|
|
|
145
|
+
# Insecure HTTP is incompatible with authentication
|
|
146
|
+
if insecure and authentication_keys is not None:
|
|
147
|
+
url_v = f"https://flower.ai/docs/framework/v{package_version}/en/"
|
|
148
|
+
page = "how-to-authenticate-supernodes.html"
|
|
149
|
+
flwr_exit(
|
|
150
|
+
ExitCode.SUPERNODE_STARTED_WITHOUT_TLS_BUT_NODE_AUTH_ENABLED,
|
|
151
|
+
"Insecure connection is enabled, but the SuperNode's private key is "
|
|
152
|
+
"provided for authentication. SuperNode authentication requires a "
|
|
153
|
+
"secure TLS connection with the SuperLink. Please enable TLS by "
|
|
154
|
+
"providing the certificate via `--root-certificates`. Please refer "
|
|
155
|
+
f"to the Flower documentation for more information: {url_v}{page}",
|
|
156
|
+
)
|
|
157
|
+
|
|
144
158
|
# Initialize factories
|
|
145
159
|
state_factory = NodeStateFactory()
|
|
146
160
|
ffs_factory = FfsFactory(get_flwr_dir(flwr_path) / "supernode" / "ffs") # type: ignore
|
{flwr_nightly-1.23.0.dev20251017.dist-info → flwr_nightly-1.23.0.dev20251020.dist-info}/METADATA
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: flwr-nightly
|
|
3
|
-
Version: 1.23.0.
|
|
3
|
+
Version: 1.23.0.dev20251020
|
|
4
4
|
Summary: Flower: A Friendly Federated AI Framework
|
|
5
5
|
License: Apache-2.0
|
|
6
6
|
Keywords: Artificial Intelligence,Federated AI,Federated Analytics,Federated Evaluation,Federated Learning,Flower,Machine Learning
|