flwr 1.25.0__py3-none-any.whl → 1.26.0__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.
- flwr/__init__.py +1 -1
- flwr/app/__init__.py +4 -1
- flwr/app/message_type.py +29 -0
- flwr/app/metadata.py +5 -2
- flwr/app/user_config.py +19 -0
- flwr/cli/app.py +37 -19
- flwr/cli/app_cmd/publish.py +25 -75
- flwr/cli/app_cmd/review.py +18 -69
- flwr/cli/auth_plugin/auth_plugin.py +5 -10
- flwr/cli/auth_plugin/noop_auth_plugin.py +1 -2
- flwr/cli/auth_plugin/oidc_cli_plugin.py +38 -38
- flwr/cli/build.py +15 -28
- flwr/cli/config/__init__.py +21 -0
- flwr/cli/config/ls.py +71 -0
- flwr/cli/config_migration.py +297 -0
- flwr/cli/config_utils.py +63 -156
- flwr/cli/constant.py +71 -0
- flwr/cli/federation/__init__.py +0 -2
- flwr/cli/federation/ls.py +256 -64
- flwr/cli/flower_config.py +429 -0
- flwr/cli/install.py +23 -62
- flwr/cli/log.py +23 -37
- flwr/cli/login/login.py +29 -63
- flwr/cli/ls.py +28 -58
- flwr/cli/new/new.py +9 -29
- flwr/cli/pull.py +19 -37
- flwr/cli/run/run.py +85 -93
- flwr/cli/run_utils.py +1 -1
- flwr/cli/stop.py +32 -73
- flwr/cli/supernode/ls.py +25 -57
- flwr/cli/supernode/register.py +31 -80
- flwr/cli/supernode/unregister.py +24 -70
- flwr/cli/typing.py +200 -0
- flwr/cli/utils.py +160 -275
- flwr/client/grpc_rere_client/connection.py +3 -3
- flwr/client/grpc_rere_client/grpc_adapter.py +1 -1
- flwr/client/message_handler/message_handler.py +2 -1
- flwr/client/mod/centraldp_mods.py +1 -1
- flwr/client/mod/localdp_mod.py +1 -1
- flwr/client/mod/secure_aggregation/secaggplus_mod.py +1 -1
- flwr/client/run_info_store.py +2 -1
- flwr/clientapp/client_app.py +2 -1
- flwr/common/__init__.py +3 -2
- flwr/common/args.py +5 -5
- flwr/common/config.py +12 -17
- flwr/common/constant.py +3 -16
- flwr/common/context.py +2 -1
- flwr/common/exit/exit.py +4 -4
- flwr/common/exit/exit_code.py +6 -0
- flwr/common/grpc.py +2 -1
- flwr/common/logger.py +1 -1
- flwr/common/message.py +1 -1
- flwr/common/retry_invoker.py +13 -5
- flwr/common/secure_aggregation/ndarrays_arithmetic.py +5 -2
- flwr/common/serde.py +7 -5
- flwr/common/telemetry.py +1 -1
- flwr/common/typing.py +4 -3
- flwr/compat/client/app.py +6 -9
- flwr/compat/client/grpc_client/connection.py +2 -1
- flwr/compat/common/constant.py +29 -0
- flwr/compat/server/app.py +1 -1
- flwr/proto/clientappio_pb2.py +2 -2
- flwr/proto/clientappio_pb2_grpc.py +104 -88
- flwr/proto/clientappio_pb2_grpc.pyi +140 -80
- flwr/proto/federation_pb2.py +5 -3
- flwr/proto/federation_pb2.pyi +32 -2
- flwr/proto/run_pb2.py +5 -13
- flwr/proto/run_pb2.pyi +0 -57
- flwr/proto/serverappio_pb2.py +2 -2
- flwr/proto/serverappio_pb2_grpc.py +138 -207
- flwr/proto/serverappio_pb2_grpc.pyi +189 -155
- flwr/proto/simulationio_pb2.py +2 -2
- flwr/proto/simulationio_pb2_grpc.py +62 -90
- flwr/proto/simulationio_pb2_grpc.pyi +95 -55
- flwr/server/app.py +6 -13
- flwr/server/compat/grid_client_proxy.py +2 -1
- flwr/server/grid/grpc_grid.py +5 -5
- flwr/server/serverapp/app.py +11 -4
- flwr/server/superlink/fleet/grpc_adapter/grpc_adapter_servicer.py +1 -1
- flwr/server/superlink/fleet/grpc_rere/node_auth_server_interceptor.py +13 -12
- flwr/server/superlink/fleet/message_handler/message_handler.py +6 -5
- flwr/server/superlink/linkstate/__init__.py +2 -2
- flwr/server/superlink/linkstate/in_memory_linkstate.py +2 -10
- flwr/server/superlink/linkstate/linkstate.py +2 -21
- flwr/server/superlink/linkstate/linkstate_factory.py +16 -8
- flwr/server/superlink/linkstate/{sqlite_linkstate.py → sql_linkstate.py} +432 -534
- flwr/server/superlink/linkstate/utils.py +49 -2
- flwr/server/superlink/serverappio/serverappio_servicer.py +1 -33
- flwr/server/superlink/simulation/simulationio_servicer.py +0 -19
- flwr/server/utils/validator.py +1 -1
- flwr/server/workflow/default_workflows.py +2 -1
- flwr/server/workflow/secure_aggregation/secaggplus_workflow.py +1 -1
- flwr/serverapp/strategy/bulyan.py +7 -1
- flwr/serverapp/strategy/dp_fixed_clipping.py +9 -1
- flwr/serverapp/strategy/fedavg.py +1 -1
- flwr/serverapp/strategy/fedxgb_cyclic.py +1 -1
- flwr/simulation/ray_transport/ray_client_proxy.py +2 -6
- flwr/simulation/run_simulation.py +3 -12
- flwr/simulation/simulationio_connection.py +3 -3
- flwr/{common → supercore}/address.py +7 -33
- flwr/supercore/app_utils.py +2 -1
- flwr/supercore/constant.py +24 -2
- flwr/supercore/corestate/{sqlite_corestate.py → sql_corestate.py} +19 -23
- flwr/supercore/credential_store/__init__.py +33 -0
- flwr/supercore/credential_store/credential_store.py +34 -0
- flwr/supercore/credential_store/file_credential_store.py +76 -0
- flwr/{common → supercore}/date.py +0 -11
- flwr/supercore/ffs/disk_ffs.py +1 -1
- flwr/supercore/object_store/object_store_factory.py +14 -6
- flwr/supercore/object_store/{sqlite_object_store.py → sql_object_store.py} +115 -117
- flwr/supercore/sql_mixin.py +315 -0
- flwr/supercore/state/__init__.py +15 -0
- flwr/supercore/state/alembic/__init__.py +15 -0
- flwr/supercore/state/alembic/env.py +103 -0
- flwr/supercore/state/alembic/script.py.mako +43 -0
- flwr/supercore/state/alembic/utils.py +239 -0
- flwr/supercore/state/alembic/versions/__init__.py +15 -0
- flwr/supercore/state/alembic/versions/rev_2026_01_28_initialize_migration_of_state_tables.py +200 -0
- flwr/supercore/state/schema/README.md +121 -0
- flwr/supercore/state/schema/__init__.py +15 -0
- flwr/supercore/state/schema/corestate_tables.py +36 -0
- flwr/supercore/state/schema/linkstate_tables.py +152 -0
- flwr/supercore/state/schema/objectstore_tables.py +90 -0
- flwr/supercore/superexec/run_superexec.py +2 -2
- flwr/supercore/utils.py +36 -1
- flwr/superlink/federation/federation_manager.py +2 -2
- flwr/superlink/federation/noop_federation_manager.py +8 -6
- flwr/superlink/servicer/control/control_servicer.py +19 -17
- flwr/supernode/cli/flower_supernode.py +2 -1
- flwr/supernode/runtime/run_clientapp.py +14 -14
- flwr/supernode/servicer/clientappio/clientappio_servicer.py +10 -8
- flwr/supernode/start_client_internal.py +10 -6
- {flwr-1.25.0.dist-info → flwr-1.26.0.dist-info}/METADATA +7 -5
- {flwr-1.25.0.dist-info → flwr-1.26.0.dist-info}/RECORD +137 -116
- flwr/cli/federation/show.py +0 -318
- flwr/common/pyproject.py +0 -42
- flwr/supercore/sqlite_mixin.py +0 -159
- /flwr/{common → supercore}/version.py +0 -0
- {flwr-1.25.0.dist-info → flwr-1.26.0.dist-info}/WHEEL +0 -0
- {flwr-1.25.0.dist-info → flwr-1.26.0.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
# Copyright 2026 Flower Labs GmbH. All Rights Reserved.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
# ==============================================================================
|
|
15
|
+
"""SQLAlchemy Core Table definitions for LinkState."""
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
from sqlalchemy import (
|
|
19
|
+
TIMESTAMP,
|
|
20
|
+
Column,
|
|
21
|
+
Float,
|
|
22
|
+
ForeignKey,
|
|
23
|
+
Index,
|
|
24
|
+
Integer,
|
|
25
|
+
LargeBinary,
|
|
26
|
+
MetaData,
|
|
27
|
+
String,
|
|
28
|
+
Table,
|
|
29
|
+
UniqueConstraint,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def create_linkstate_metadata() -> MetaData:
|
|
34
|
+
"""Create and return MetaData with LinkState table definitions."""
|
|
35
|
+
metadata = MetaData()
|
|
36
|
+
|
|
37
|
+
# --------------------------------------------------------------------------
|
|
38
|
+
# Table: node
|
|
39
|
+
# --------------------------------------------------------------------------
|
|
40
|
+
Table(
|
|
41
|
+
"node",
|
|
42
|
+
metadata,
|
|
43
|
+
Column("node_id", Integer, unique=True),
|
|
44
|
+
Column("owner_aid", String),
|
|
45
|
+
Column("owner_name", String),
|
|
46
|
+
Column("status", String),
|
|
47
|
+
Column("registered_at", String),
|
|
48
|
+
Column("last_activated_at", String, nullable=True),
|
|
49
|
+
Column("last_deactivated_at", String, nullable=True),
|
|
50
|
+
Column("unregistered_at", String, nullable=True),
|
|
51
|
+
Column("online_until", TIMESTAMP, nullable=True),
|
|
52
|
+
Column("heartbeat_interval", Float),
|
|
53
|
+
Column("public_key", LargeBinary, unique=True),
|
|
54
|
+
# Indexes
|
|
55
|
+
# Used in delete_node and get_node_info (security/filtering)
|
|
56
|
+
Index("idx_node_owner_aid", "owner_aid"),
|
|
57
|
+
# Used in get_nodes and activation checks (frequent filtering)
|
|
58
|
+
Index("idx_node_status", "status"),
|
|
59
|
+
# Used in heartbeat checks to efficiently find expired nodes
|
|
60
|
+
Index("idx_online_until", "online_until"),
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
# --------------------------------------------------------------------------
|
|
64
|
+
# Table: run
|
|
65
|
+
# --------------------------------------------------------------------------
|
|
66
|
+
Table(
|
|
67
|
+
"run",
|
|
68
|
+
metadata,
|
|
69
|
+
Column("run_id", Integer, unique=True),
|
|
70
|
+
Column("fab_id", String),
|
|
71
|
+
Column("fab_version", String),
|
|
72
|
+
Column("fab_hash", String),
|
|
73
|
+
Column("override_config", String),
|
|
74
|
+
Column("pending_at", String),
|
|
75
|
+
Column("starting_at", String),
|
|
76
|
+
Column("running_at", String),
|
|
77
|
+
Column("finished_at", String),
|
|
78
|
+
Column("sub_status", String),
|
|
79
|
+
Column("details", String),
|
|
80
|
+
Column("federation", String),
|
|
81
|
+
Column("federation_options", LargeBinary),
|
|
82
|
+
Column("flwr_aid", String),
|
|
83
|
+
Column("bytes_sent", Integer, server_default="0"),
|
|
84
|
+
Column("bytes_recv", Integer, server_default="0"),
|
|
85
|
+
Column("clientapp_runtime", Float, server_default="0.0"),
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
# --------------------------------------------------------------------------
|
|
89
|
+
# Table: logs
|
|
90
|
+
# --------------------------------------------------------------------------
|
|
91
|
+
Table(
|
|
92
|
+
"logs",
|
|
93
|
+
metadata,
|
|
94
|
+
Column("timestamp", Float),
|
|
95
|
+
Column("run_id", Integer, ForeignKey("run.run_id")),
|
|
96
|
+
Column("node_id", Integer),
|
|
97
|
+
Column("log", String),
|
|
98
|
+
# Composite PK
|
|
99
|
+
UniqueConstraint("timestamp", "run_id", "node_id"),
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
# --------------------------------------------------------------------------
|
|
103
|
+
# Table: context
|
|
104
|
+
# --------------------------------------------------------------------------
|
|
105
|
+
Table(
|
|
106
|
+
"context",
|
|
107
|
+
metadata,
|
|
108
|
+
Column("run_id", Integer, ForeignKey("run.run_id"), unique=True),
|
|
109
|
+
Column("context", LargeBinary),
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
# --------------------------------------------------------------------------
|
|
113
|
+
# Table: message_ins
|
|
114
|
+
# --------------------------------------------------------------------------
|
|
115
|
+
Table(
|
|
116
|
+
"message_ins",
|
|
117
|
+
metadata,
|
|
118
|
+
Column("message_id", String, unique=True),
|
|
119
|
+
Column("group_id", String),
|
|
120
|
+
Column("run_id", Integer, ForeignKey("run.run_id")),
|
|
121
|
+
Column("src_node_id", Integer),
|
|
122
|
+
Column("dst_node_id", Integer),
|
|
123
|
+
Column("reply_to_message_id", String),
|
|
124
|
+
Column("created_at", Float),
|
|
125
|
+
Column("delivered_at", String),
|
|
126
|
+
Column("ttl", Float),
|
|
127
|
+
Column("message_type", String),
|
|
128
|
+
Column("content", LargeBinary, nullable=True),
|
|
129
|
+
Column("error", LargeBinary, nullable=True),
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
# --------------------------------------------------------------------------
|
|
133
|
+
# Table: message_res
|
|
134
|
+
# --------------------------------------------------------------------------
|
|
135
|
+
Table(
|
|
136
|
+
"message_res",
|
|
137
|
+
metadata,
|
|
138
|
+
Column("message_id", String, unique=True),
|
|
139
|
+
Column("group_id", String),
|
|
140
|
+
Column("run_id", Integer, ForeignKey("run.run_id")),
|
|
141
|
+
Column("src_node_id", Integer),
|
|
142
|
+
Column("dst_node_id", Integer),
|
|
143
|
+
Column("reply_to_message_id", String),
|
|
144
|
+
Column("created_at", Float),
|
|
145
|
+
Column("delivered_at", String),
|
|
146
|
+
Column("ttl", Float),
|
|
147
|
+
Column("message_type", String),
|
|
148
|
+
Column("content", LargeBinary, nullable=True),
|
|
149
|
+
Column("error", LargeBinary, nullable=True),
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
return metadata
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# Copyright 2026 Flower Labs GmbH. All Rights Reserved.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
# ==============================================================================
|
|
15
|
+
"""SQLAlchemy Core Table definitions for ObjectStore."""
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
from sqlalchemy import (
|
|
19
|
+
CheckConstraint,
|
|
20
|
+
Column,
|
|
21
|
+
ForeignKey,
|
|
22
|
+
Integer,
|
|
23
|
+
LargeBinary,
|
|
24
|
+
MetaData,
|
|
25
|
+
PrimaryKeyConstraint,
|
|
26
|
+
String,
|
|
27
|
+
Table,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def create_objectstore_metadata() -> MetaData:
|
|
32
|
+
"""Create and return MetaData with ObjectStore table definitions."""
|
|
33
|
+
metadata = MetaData()
|
|
34
|
+
|
|
35
|
+
# --------------------------------------------------------------------------
|
|
36
|
+
# Table: objects
|
|
37
|
+
# --------------------------------------------------------------------------
|
|
38
|
+
Table(
|
|
39
|
+
"objects",
|
|
40
|
+
metadata,
|
|
41
|
+
Column("object_id", String, primary_key=True, nullable=True),
|
|
42
|
+
Column("content", LargeBinary),
|
|
43
|
+
Column(
|
|
44
|
+
"is_available",
|
|
45
|
+
Integer,
|
|
46
|
+
nullable=False,
|
|
47
|
+
server_default="0",
|
|
48
|
+
),
|
|
49
|
+
Column("ref_count", Integer, nullable=False, server_default="0"),
|
|
50
|
+
CheckConstraint("is_available IN (0, 1)", name="ck_objects_is_available"),
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
# --------------------------------------------------------------------------
|
|
54
|
+
# Table: object_children
|
|
55
|
+
# --------------------------------------------------------------------------
|
|
56
|
+
Table(
|
|
57
|
+
"object_children",
|
|
58
|
+
metadata,
|
|
59
|
+
Column(
|
|
60
|
+
"parent_id",
|
|
61
|
+
String,
|
|
62
|
+
ForeignKey("objects.object_id", ondelete="CASCADE"),
|
|
63
|
+
nullable=False,
|
|
64
|
+
),
|
|
65
|
+
Column(
|
|
66
|
+
"child_id",
|
|
67
|
+
String,
|
|
68
|
+
ForeignKey("objects.object_id", ondelete="CASCADE"),
|
|
69
|
+
nullable=False,
|
|
70
|
+
),
|
|
71
|
+
PrimaryKeyConstraint("parent_id", "child_id"),
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
# --------------------------------------------------------------------------
|
|
75
|
+
# Table: run_objects
|
|
76
|
+
# --------------------------------------------------------------------------
|
|
77
|
+
Table(
|
|
78
|
+
"run_objects",
|
|
79
|
+
metadata,
|
|
80
|
+
Column("run_id", Integer, nullable=False),
|
|
81
|
+
Column(
|
|
82
|
+
"object_id",
|
|
83
|
+
String,
|
|
84
|
+
ForeignKey("objects.object_id", ondelete="CASCADE"),
|
|
85
|
+
nullable=False,
|
|
86
|
+
),
|
|
87
|
+
PrimaryKeyConstraint("run_id", "object_id"),
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
return metadata
|
|
@@ -23,7 +23,7 @@ from flwr.common.config import get_flwr_dir
|
|
|
23
23
|
from flwr.common.exit import ExitCode, flwr_exit, register_signal_handlers
|
|
24
24
|
from flwr.common.grpc import create_channel, on_channel_state_change
|
|
25
25
|
from flwr.common.logger import log
|
|
26
|
-
from flwr.common.retry_invoker import
|
|
26
|
+
from flwr.common.retry_invoker import make_simple_grpc_retry_invoker, wrap_stub
|
|
27
27
|
from flwr.common.serde import run_from_proto
|
|
28
28
|
from flwr.common.telemetry import EventType
|
|
29
29
|
from flwr.common.typing import Run
|
|
@@ -101,7 +101,7 @@ def run_superexec( # pylint: disable=R0913,R0914,R0917
|
|
|
101
101
|
|
|
102
102
|
# Create the gRPC stub for the AppIO API
|
|
103
103
|
stub = stub_class(channel)
|
|
104
|
-
|
|
104
|
+
wrap_stub(stub, make_simple_grpc_retry_invoker())
|
|
105
105
|
|
|
106
106
|
def get_run(run_id: int) -> Run:
|
|
107
107
|
_req = GetRunRequest(run_id=run_id)
|
flwr/supercore/utils.py
CHANGED
|
@@ -16,11 +16,14 @@
|
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
import json
|
|
19
|
+
import os
|
|
19
20
|
import re
|
|
21
|
+
from pathlib import Path
|
|
20
22
|
|
|
21
23
|
import requests
|
|
22
24
|
|
|
23
|
-
from flwr.common.
|
|
25
|
+
from flwr.common.constant import FLWR_DIR, FLWR_HOME
|
|
26
|
+
from flwr.supercore.version import package_version as flwr_version
|
|
24
27
|
|
|
25
28
|
from .constant import APP_ID_PATTERN, APP_VERSION_PATTERN
|
|
26
29
|
|
|
@@ -62,6 +65,17 @@ def int64_to_uint64(signed: int) -> int:
|
|
|
62
65
|
return signed
|
|
63
66
|
|
|
64
67
|
|
|
68
|
+
def get_flwr_home() -> Path:
|
|
69
|
+
"""Get the Flower home directory path.
|
|
70
|
+
|
|
71
|
+
Returns FLWR_HOME environment variable if set, otherwise returns a default
|
|
72
|
+
subdirectory in the user's home directory.
|
|
73
|
+
"""
|
|
74
|
+
if flwr_home := os.getenv(FLWR_HOME):
|
|
75
|
+
return Path(flwr_home)
|
|
76
|
+
return Path.home() / FLWR_DIR
|
|
77
|
+
|
|
78
|
+
|
|
65
79
|
def parse_app_spec(app_spec: str) -> tuple[str, str | None]:
|
|
66
80
|
"""Parse app specification string into app ID and version.
|
|
67
81
|
|
|
@@ -240,3 +254,24 @@ def humanize_bytes(num_bytes: int) -> str:
|
|
|
240
254
|
value /= 1024
|
|
241
255
|
|
|
242
256
|
raise RuntimeError("Unreachable code") # Make mypy happy
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def check_federation_format(federation: str) -> None:
|
|
260
|
+
"""Check if the federation string is valid.
|
|
261
|
+
|
|
262
|
+
Parameters
|
|
263
|
+
----------
|
|
264
|
+
federation : str
|
|
265
|
+
The federation string to check.
|
|
266
|
+
|
|
267
|
+
Raises
|
|
268
|
+
------
|
|
269
|
+
ValueError
|
|
270
|
+
If the federation string is not valid. The expected
|
|
271
|
+
format is '@<account-name>/<federation-name>'.
|
|
272
|
+
"""
|
|
273
|
+
if not re.match(r"^@[a-zA-Z0-9\-_]+/[a-zA-Z0-9\-_]+$", federation):
|
|
274
|
+
raise ValueError(
|
|
275
|
+
f"Invalid federation format: {federation}. "
|
|
276
|
+
f"Expected format: '@<account-name>/<federation-name>'."
|
|
277
|
+
)
|
|
@@ -56,8 +56,8 @@ class FederationManager(ABC):
|
|
|
56
56
|
"""Given a node ID, check if it is in the federation."""
|
|
57
57
|
|
|
58
58
|
@abstractmethod
|
|
59
|
-
def get_federations(self, flwr_aid: str) -> list[str]:
|
|
60
|
-
"""Get federations of which the account is a member."""
|
|
59
|
+
def get_federations(self, flwr_aid: str) -> list[tuple[str, str]]:
|
|
60
|
+
"""Get federations (name, description) of which the account is a member."""
|
|
61
61
|
|
|
62
62
|
@abstractmethod
|
|
63
63
|
def get_details(self, federation: str) -> Federation:
|
|
@@ -15,9 +15,10 @@
|
|
|
15
15
|
"""NoOp implementation of FederationManager."""
|
|
16
16
|
|
|
17
17
|
|
|
18
|
-
from flwr.common.constant import NOOP_FLWR_AID
|
|
18
|
+
from flwr.common.constant import NOOP_ACCOUNT_NAME, NOOP_FLWR_AID
|
|
19
19
|
from flwr.common.typing import Federation
|
|
20
|
-
from flwr.
|
|
20
|
+
from flwr.proto.federation_pb2 import Account # pylint: disable=E0611
|
|
21
|
+
from flwr.supercore.constant import NOOP_FEDERATION, NOOP_FEDERATION_DESCRIPTION
|
|
21
22
|
|
|
22
23
|
from .federation_manager import FederationManager
|
|
23
24
|
|
|
@@ -47,11 +48,11 @@ class NoOpFederationManager(FederationManager):
|
|
|
47
48
|
raise ValueError(f"Federation '{federation}' does not exist.")
|
|
48
49
|
return True
|
|
49
50
|
|
|
50
|
-
def get_federations(self, flwr_aid: str) -> list[str]:
|
|
51
|
-
"""Get federations of which the account is a member."""
|
|
51
|
+
def get_federations(self, flwr_aid: str) -> list[tuple[str, str]]:
|
|
52
|
+
"""Get federations (name, description) of which the account is a member."""
|
|
52
53
|
if flwr_aid != NOOP_FLWR_AID:
|
|
53
54
|
return []
|
|
54
|
-
return [NOOP_FEDERATION]
|
|
55
|
+
return [(NOOP_FEDERATION, NOOP_FEDERATION_DESCRIPTION)]
|
|
55
56
|
|
|
56
57
|
def get_details(self, federation: str) -> Federation:
|
|
57
58
|
"""Get details of the federation."""
|
|
@@ -65,7 +66,8 @@ class NoOpFederationManager(FederationManager):
|
|
|
65
66
|
]
|
|
66
67
|
return Federation(
|
|
67
68
|
name=NOOP_FEDERATION,
|
|
68
|
-
|
|
69
|
+
description=NOOP_FEDERATION_DESCRIPTION,
|
|
70
|
+
accounts=[Account(id=NOOP_FLWR_AID, name=NOOP_ACCOUNT_NAME)],
|
|
69
71
|
nodes=nodes,
|
|
70
72
|
runs=runs,
|
|
71
73
|
)
|
|
@@ -29,6 +29,8 @@ from flwr.cli.config_utils import get_fab_metadata
|
|
|
29
29
|
from flwr.common import Context, RecordDict, now
|
|
30
30
|
from flwr.common.constant import (
|
|
31
31
|
FAB_MAX_SIZE,
|
|
32
|
+
FEDERATION_NOT_FOUND_MESSAGE,
|
|
33
|
+
FEDERATION_NOT_SPECIFIED_MESSAGE,
|
|
32
34
|
HEARTBEAT_DEFAULT_INTERVAL,
|
|
33
35
|
LOG_STREAM_INTERVAL,
|
|
34
36
|
NO_ACCOUNT_AUTH_MESSAGE,
|
|
@@ -38,6 +40,7 @@ from flwr.common.constant import (
|
|
|
38
40
|
PUBLIC_KEY_NOT_VALID,
|
|
39
41
|
PULL_UNFINISHED_RUN_MESSAGE,
|
|
40
42
|
RUN_ID_NOT_FOUND_MESSAGE,
|
|
43
|
+
SUPERLINK_NODE_ID,
|
|
41
44
|
TRANSPORT_TYPE_GRPC_ADAPTER,
|
|
42
45
|
Status,
|
|
43
46
|
SubStatus,
|
|
@@ -79,7 +82,7 @@ from flwr.proto.control_pb2 import ( # pylint: disable=E0611
|
|
|
79
82
|
from flwr.proto.federation_pb2 import Federation # pylint: disable=E0611
|
|
80
83
|
from flwr.proto.node_pb2 import NodeInfo # pylint: disable=E0611
|
|
81
84
|
from flwr.server.superlink.linkstate import LinkState, LinkStateFactory
|
|
82
|
-
from flwr.supercore.constant import PLATFORM_API_URL
|
|
85
|
+
from flwr.supercore.constant import NOOP_FEDERATION, PLATFORM_API_URL
|
|
83
86
|
from flwr.supercore.ffs import FfsFactory
|
|
84
87
|
from flwr.supercore.object_store import ObjectStore, ObjectStoreFactory
|
|
85
88
|
from flwr.supercore.primitives.asymmetric import bytes_to_public_key, uses_nist_ec_curve
|
|
@@ -148,10 +151,11 @@ class ControlServicer(control_pb2_grpc.ControlServicer):
|
|
|
148
151
|
)
|
|
149
152
|
|
|
150
153
|
# Check (1) federation exists and (2) the flwr_aid is a member
|
|
151
|
-
federation = request.federation
|
|
152
|
-
|
|
154
|
+
federation = request.federation or NOOP_FEDERATION
|
|
153
155
|
if not state.federation_manager.exists(federation):
|
|
154
|
-
|
|
156
|
+
if request.federation:
|
|
157
|
+
raise ValueError(FEDERATION_NOT_FOUND_MESSAGE % federation)
|
|
158
|
+
raise ValueError(FEDERATION_NOT_SPECIFIED_MESSAGE)
|
|
155
159
|
|
|
156
160
|
if not state.federation_manager.has_member(flwr_aid, federation):
|
|
157
161
|
raise ValueError(
|
|
@@ -169,7 +173,7 @@ class ControlServicer(control_pb2_grpc.ControlServicer):
|
|
|
169
173
|
fab_hash = ffs.put(fab.content, fab.verifications)
|
|
170
174
|
|
|
171
175
|
if fab_hash != fab.hash_str:
|
|
172
|
-
raise
|
|
176
|
+
raise ValueError(
|
|
173
177
|
f"FAB ({fab.hash_str}) hash from request doesn't match contents"
|
|
174
178
|
)
|
|
175
179
|
fab_id, fab_version = get_fab_metadata(fab.content)
|
|
@@ -179,7 +183,7 @@ class ControlServicer(control_pb2_grpc.ControlServicer):
|
|
|
179
183
|
fab_version,
|
|
180
184
|
fab_hash,
|
|
181
185
|
override_config,
|
|
182
|
-
|
|
186
|
+
federation,
|
|
183
187
|
federation_options,
|
|
184
188
|
flwr_aid,
|
|
185
189
|
)
|
|
@@ -195,7 +199,7 @@ class ControlServicer(control_pb2_grpc.ControlServicer):
|
|
|
195
199
|
# Create an empty context for the Run
|
|
196
200
|
context = Context(
|
|
197
201
|
run_id=run_id,
|
|
198
|
-
node_id=
|
|
202
|
+
node_id=SUPERLINK_NODE_ID,
|
|
199
203
|
# Dict is invariant in mypy
|
|
200
204
|
node_config=node_config, # type: ignore[arg-type]
|
|
201
205
|
state=RecordDict(),
|
|
@@ -205,13 +209,9 @@ class ControlServicer(control_pb2_grpc.ControlServicer):
|
|
|
205
209
|
# Register the context at the LinkState
|
|
206
210
|
state.set_serverapp_context(run_id=run_id, context=context)
|
|
207
211
|
|
|
208
|
-
|
|
209
|
-
except Exception as e:
|
|
212
|
+
except ValueError as e:
|
|
210
213
|
log(ERROR, "Could not start run: %s", str(e))
|
|
211
|
-
context.abort(
|
|
212
|
-
grpc.StatusCode.FAILED_PRECONDITION,
|
|
213
|
-
str(e),
|
|
214
|
-
)
|
|
214
|
+
context.abort(grpc.StatusCode.FAILED_PRECONDITION, str(e))
|
|
215
215
|
|
|
216
216
|
log(INFO, "Created run %s", str(run_id))
|
|
217
217
|
return StartRunResponse(run_id=run_id)
|
|
@@ -536,9 +536,10 @@ class ControlServicer(control_pb2_grpc.ControlServicer):
|
|
|
536
536
|
|
|
537
537
|
# Get federations the account is a member of
|
|
538
538
|
federations = state.federation_manager.get_federations(flwr_aid=flwr_aid)
|
|
539
|
-
|
|
540
539
|
return ListFederationsResponse(
|
|
541
|
-
federations=[
|
|
540
|
+
federations=[
|
|
541
|
+
Federation(name=fed[0], description=fed[1]) for fed in federations
|
|
542
|
+
]
|
|
542
543
|
)
|
|
543
544
|
|
|
544
545
|
def ShowFederation(
|
|
@@ -558,7 +559,7 @@ class ControlServicer(control_pb2_grpc.ControlServicer):
|
|
|
558
559
|
|
|
559
560
|
# Ensure flwr_aid is a member of the requested federation
|
|
560
561
|
federation = request.federation_name
|
|
561
|
-
if federation not in federations:
|
|
562
|
+
if federation not in [fed[0] for fed in federations]:
|
|
562
563
|
context.abort(
|
|
563
564
|
grpc.StatusCode.FAILED_PRECONDITION,
|
|
564
565
|
f"Federation '{federation}' does not exist or you are "
|
|
@@ -571,7 +572,8 @@ class ControlServicer(control_pb2_grpc.ControlServicer):
|
|
|
571
572
|
# Build Federation proto object
|
|
572
573
|
federation_proto = Federation(
|
|
573
574
|
name=federation,
|
|
574
|
-
member_aids=details.
|
|
575
|
+
member_aids=[acc.id for acc in details.accounts], # Deprecated in v1.26.0
|
|
576
|
+
accounts=details.accounts,
|
|
575
577
|
nodes=details.nodes,
|
|
576
578
|
runs=[run_to_proto(run) for run in details.runs],
|
|
577
579
|
)
|
|
@@ -233,7 +233,7 @@ def _try_setup_client_authentication(
|
|
|
233
233
|
|
|
234
234
|
try:
|
|
235
235
|
ssh_private_key = load_ssh_private_key(
|
|
236
|
-
Path(args.auth_supernode_private_key).read_bytes(),
|
|
236
|
+
Path(args.auth_supernode_private_key).expanduser().read_bytes(),
|
|
237
237
|
None,
|
|
238
238
|
)
|
|
239
239
|
if not isinstance(ssh_private_key, ec.EllipticCurvePrivateKey):
|
|
@@ -260,6 +260,7 @@ def _try_obtain_trusted_entities(
|
|
|
260
260
|
"""Validate and return the trust entities."""
|
|
261
261
|
if not trusted_entities_path:
|
|
262
262
|
return None
|
|
263
|
+
trusted_entities_path = trusted_entities_path.expanduser()
|
|
263
264
|
if not trusted_entities_path.is_file():
|
|
264
265
|
flwr_exit(
|
|
265
266
|
ExitCode.SUPERNODE_INVALID_TRUSTED_ENTITIES,
|
|
@@ -41,7 +41,7 @@ from flwr.common.inflatable_protobuf_utils import (
|
|
|
41
41
|
from flwr.common.inflatable_utils import pull_and_inflate_object_from_tree, push_objects
|
|
42
42
|
from flwr.common.logger import log
|
|
43
43
|
from flwr.common.message import remove_content_from_message
|
|
44
|
-
from flwr.common.retry_invoker import
|
|
44
|
+
from flwr.common.retry_invoker import make_simple_grpc_retry_invoker, wrap_stub
|
|
45
45
|
from flwr.common.serde import (
|
|
46
46
|
context_from_proto,
|
|
47
47
|
context_to_proto,
|
|
@@ -103,14 +103,14 @@ def run_clientapp( # pylint: disable=R0913, R0914, R0917
|
|
|
103
103
|
flwr_dir_ = get_flwr_dir(flwr_dir)
|
|
104
104
|
try:
|
|
105
105
|
stub = ClientAppIoStub(channel)
|
|
106
|
-
|
|
106
|
+
wrap_stub(stub, make_simple_grpc_retry_invoker())
|
|
107
107
|
|
|
108
108
|
# Start app heartbeat
|
|
109
109
|
heartbeat_sender = HeartbeatSender(make_app_heartbeat_fn_grpc(stub, token))
|
|
110
110
|
heartbeat_sender.start()
|
|
111
111
|
|
|
112
112
|
# Pull Message, Context, Run and (optional) FAB from SuperNode
|
|
113
|
-
message, context, run, fab =
|
|
113
|
+
message, context, run, fab = pull_appinputs(stub=stub, token=token)
|
|
114
114
|
|
|
115
115
|
try:
|
|
116
116
|
|
|
@@ -152,7 +152,7 @@ def run_clientapp( # pylint: disable=R0913, R0914, R0917
|
|
|
152
152
|
reply_message = Message(Error(code=e_code, reason=reason), reply_to=message)
|
|
153
153
|
|
|
154
154
|
# Push Message and Context to SuperNode
|
|
155
|
-
_ =
|
|
155
|
+
_ = push_appoutputs(
|
|
156
156
|
stub=stub, token=token, message=reply_message, context=context
|
|
157
157
|
)
|
|
158
158
|
|
|
@@ -165,15 +165,15 @@ def run_clientapp( # pylint: disable=R0913, R0914, R0917
|
|
|
165
165
|
)
|
|
166
166
|
|
|
167
167
|
|
|
168
|
-
def
|
|
168
|
+
def pull_appinputs(
|
|
169
169
|
stub: ClientAppIoStub, token: str
|
|
170
170
|
) -> tuple[Message, Context, Run, Fab | None]:
|
|
171
|
-
"""Pull
|
|
171
|
+
"""Pull AppInputs from SuperNode."""
|
|
172
172
|
masked_token = mask_string(token)
|
|
173
|
-
log(INFO, "[flwr-clientapp] Pull `
|
|
173
|
+
log(INFO, "[flwr-clientapp] Pull `AppInputs` for token %s", masked_token)
|
|
174
174
|
try:
|
|
175
175
|
# Pull Context, Run and (optional) FAB
|
|
176
|
-
res: PullAppInputsResponse = stub.
|
|
176
|
+
res: PullAppInputsResponse = stub.PullAppInputs(
|
|
177
177
|
PullAppInputsRequest(token=token)
|
|
178
178
|
)
|
|
179
179
|
context = context_from_proto(res.context)
|
|
@@ -201,16 +201,16 @@ def pull_clientappinputs(
|
|
|
201
201
|
message.metadata.__dict__["_message_id"] = object_tree.object_id
|
|
202
202
|
return message, context, run, fab
|
|
203
203
|
except grpc.RpcError as e:
|
|
204
|
-
log(ERROR, "[
|
|
204
|
+
log(ERROR, "[PullAppInputs] gRPC error occurred: %s", str(e))
|
|
205
205
|
raise e
|
|
206
206
|
|
|
207
207
|
|
|
208
|
-
def
|
|
208
|
+
def push_appoutputs(
|
|
209
209
|
stub: ClientAppIoStub, token: str, message: Message, context: Context
|
|
210
210
|
) -> PushAppOutputsResponse:
|
|
211
|
-
"""Push
|
|
211
|
+
"""Push AppOutputs to SuperNode."""
|
|
212
212
|
masked_token = mask_string(token)
|
|
213
|
-
log(INFO, "[flwr-clientapp] Push `
|
|
213
|
+
log(INFO, "[flwr-clientapp] Push `AppOutputs` for token %s", masked_token)
|
|
214
214
|
# Set message ID
|
|
215
215
|
message.metadata.__dict__["_message_id"] = message.object_id
|
|
216
216
|
proto_message = message_to_proto(remove_content_from_message(message))
|
|
@@ -250,10 +250,10 @@ def push_clientappoutputs(
|
|
|
250
250
|
)
|
|
251
251
|
|
|
252
252
|
# Push Context
|
|
253
|
-
res: PushAppOutputsResponse = stub.
|
|
253
|
+
res: PushAppOutputsResponse = stub.PushAppOutputs(
|
|
254
254
|
PushAppOutputsRequest(token=token, context=proto_context)
|
|
255
255
|
)
|
|
256
256
|
return res
|
|
257
257
|
except grpc.RpcError as e:
|
|
258
|
-
log(ERROR, "[
|
|
258
|
+
log(ERROR, "[PushAppOutputs] gRPC error occurred: %s", str(e))
|
|
259
259
|
raise e
|
|
@@ -129,11 +129,11 @@ class ClientAppIoServicer(clientappio_pb2_grpc.ClientAppIoServicer):
|
|
|
129
129
|
|
|
130
130
|
return GetRunResponse(run=run_to_proto(run))
|
|
131
131
|
|
|
132
|
-
def
|
|
132
|
+
def PullAppInputs(
|
|
133
133
|
self, request: PullAppInputsRequest, context: grpc.ServicerContext
|
|
134
134
|
) -> PullAppInputsResponse:
|
|
135
135
|
"""Pull Message, Context, and Run."""
|
|
136
|
-
log(DEBUG, "ClientAppIo.
|
|
136
|
+
log(DEBUG, "ClientAppIo.PullAppInputs")
|
|
137
137
|
|
|
138
138
|
# Initialize state and ffs connection
|
|
139
139
|
state = self.state_factory.state()
|
|
@@ -176,11 +176,11 @@ class ClientAppIoServicer(clientappio_pb2_grpc.ClientAppIoServicer):
|
|
|
176
176
|
fab=fab_to_proto(fab),
|
|
177
177
|
)
|
|
178
178
|
|
|
179
|
-
def
|
|
179
|
+
def PushAppOutputs(
|
|
180
180
|
self, request: PushAppOutputsRequest, context: grpc.ServicerContext
|
|
181
181
|
) -> PushAppOutputsResponse:
|
|
182
182
|
"""Push Message and Context."""
|
|
183
|
-
log(DEBUG, "ClientAppIo.
|
|
183
|
+
log(DEBUG, "ClientAppIo.PushAppOutputs")
|
|
184
184
|
|
|
185
185
|
# Initialize state connection
|
|
186
186
|
state = self.state_factory.state()
|
|
@@ -251,16 +251,18 @@ class ClientAppIoServicer(clientappio_pb2_grpc.ClientAppIoServicer):
|
|
|
251
251
|
)
|
|
252
252
|
raise RuntimeError("This line should never be reached.")
|
|
253
253
|
|
|
254
|
+
# Record message processing end time
|
|
255
|
+
state.record_message_processing_end(
|
|
256
|
+
message_id=request.messages_list[0].metadata.reply_to_message_id
|
|
257
|
+
)
|
|
258
|
+
|
|
254
259
|
# Store Message object to descendants mapping and preregister objects
|
|
255
260
|
objects_to_push: set[str] = set()
|
|
256
261
|
for object_tree in request.message_object_trees:
|
|
257
262
|
objects_to_push |= set(store.preregister(run_id, object_tree))
|
|
263
|
+
|
|
258
264
|
# Save the message to the state
|
|
259
265
|
state.store_message(message_from_proto(request.messages_list[0]))
|
|
260
|
-
# Record message processing end time
|
|
261
|
-
state.record_message_processing_end(
|
|
262
|
-
message_id=request.messages_list[0].metadata.reply_to_message_id
|
|
263
|
-
)
|
|
264
266
|
return PushAppMessagesResponse(objects_to_push=objects_to_push)
|
|
265
267
|
|
|
266
268
|
def SendAppHeartbeat(
|