flwr 1.20.0__py3-none-any.whl → 1.22.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 +4 -1
- flwr/app/__init__.py +28 -0
- flwr/app/exception.py +31 -0
- flwr/cli/app.py +2 -0
- flwr/cli/auth_plugin/oidc_cli_plugin.py +4 -4
- flwr/cli/cli_user_auth_interceptor.py +1 -1
- flwr/cli/config_utils.py +3 -3
- flwr/cli/constant.py +25 -8
- flwr/cli/log.py +9 -9
- flwr/cli/login/login.py +3 -3
- flwr/cli/ls.py +5 -5
- flwr/cli/new/new.py +15 -2
- flwr/cli/new/templates/app/README.flowertune.md.tpl +1 -1
- flwr/cli/new/templates/app/code/__init__.pytorch_legacy_api.py.tpl +1 -0
- flwr/cli/new/templates/app/code/client.baseline.py.tpl +64 -47
- flwr/cli/new/templates/app/code/client.huggingface.py.tpl +68 -30
- flwr/cli/new/templates/app/code/client.jax.py.tpl +63 -42
- flwr/cli/new/templates/app/code/client.mlx.py.tpl +80 -51
- flwr/cli/new/templates/app/code/client.numpy.py.tpl +36 -13
- flwr/cli/new/templates/app/code/client.pytorch.py.tpl +71 -46
- flwr/cli/new/templates/app/code/client.pytorch_legacy_api.py.tpl +55 -0
- flwr/cli/new/templates/app/code/client.sklearn.py.tpl +75 -30
- flwr/cli/new/templates/app/code/client.tensorflow.py.tpl +69 -44
- flwr/cli/new/templates/app/code/client.xgboost.py.tpl +110 -0
- flwr/cli/new/templates/app/code/flwr_tune/client_app.py.tpl +56 -90
- flwr/cli/new/templates/app/code/flwr_tune/models.py.tpl +1 -23
- flwr/cli/new/templates/app/code/flwr_tune/server_app.py.tpl +37 -58
- flwr/cli/new/templates/app/code/flwr_tune/strategy.py.tpl +39 -44
- flwr/cli/new/templates/app/code/model.baseline.py.tpl +0 -14
- flwr/cli/new/templates/app/code/server.baseline.py.tpl +27 -29
- flwr/cli/new/templates/app/code/server.huggingface.py.tpl +23 -19
- flwr/cli/new/templates/app/code/server.jax.py.tpl +27 -14
- flwr/cli/new/templates/app/code/server.mlx.py.tpl +29 -19
- flwr/cli/new/templates/app/code/server.numpy.py.tpl +30 -17
- flwr/cli/new/templates/app/code/server.pytorch.py.tpl +36 -26
- flwr/cli/new/templates/app/code/server.pytorch_legacy_api.py.tpl +31 -0
- flwr/cli/new/templates/app/code/server.sklearn.py.tpl +29 -21
- flwr/cli/new/templates/app/code/server.tensorflow.py.tpl +28 -19
- flwr/cli/new/templates/app/code/server.xgboost.py.tpl +56 -0
- flwr/cli/new/templates/app/code/task.huggingface.py.tpl +16 -20
- flwr/cli/new/templates/app/code/task.jax.py.tpl +1 -1
- flwr/cli/new/templates/app/code/task.numpy.py.tpl +1 -1
- flwr/cli/new/templates/app/code/task.pytorch.py.tpl +14 -27
- flwr/cli/new/templates/app/code/task.pytorch_legacy_api.py.tpl +111 -0
- flwr/cli/new/templates/app/code/task.tensorflow.py.tpl +1 -2
- flwr/cli/new/templates/app/code/task.xgboost.py.tpl +67 -0
- flwr/cli/new/templates/app/pyproject.baseline.toml.tpl +4 -4
- flwr/cli/new/templates/app/pyproject.flowertune.toml.tpl +2 -2
- flwr/cli/new/templates/app/pyproject.huggingface.toml.tpl +4 -4
- flwr/cli/new/templates/app/pyproject.jax.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.mlx.toml.tpl +2 -2
- flwr/cli/new/templates/app/pyproject.numpy.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl +3 -3
- flwr/cli/new/templates/app/pyproject.pytorch_legacy_api.toml.tpl +53 -0
- flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.xgboost.toml.tpl +61 -0
- flwr/cli/pull.py +100 -0
- flwr/cli/run/run.py +9 -13
- flwr/cli/stop.py +7 -4
- flwr/cli/utils.py +36 -8
- flwr/client/grpc_rere_client/connection.py +1 -12
- flwr/client/rest_client/connection.py +3 -0
- flwr/clientapp/__init__.py +10 -0
- flwr/clientapp/mod/__init__.py +29 -0
- flwr/clientapp/mod/centraldp_mods.py +248 -0
- flwr/clientapp/mod/localdp_mod.py +169 -0
- flwr/clientapp/typing.py +22 -0
- flwr/common/args.py +20 -6
- flwr/common/auth_plugin/__init__.py +4 -4
- flwr/common/auth_plugin/auth_plugin.py +7 -7
- flwr/common/constant.py +26 -4
- flwr/common/event_log_plugin/event_log_plugin.py +1 -1
- flwr/common/exit/__init__.py +4 -0
- flwr/common/exit/exit.py +8 -1
- flwr/common/exit/exit_code.py +30 -7
- flwr/common/exit/exit_handler.py +62 -0
- flwr/common/{exit_handlers.py → exit/signal_handler.py} +20 -37
- flwr/common/grpc.py +0 -11
- flwr/common/inflatable_utils.py +1 -1
- flwr/common/logger.py +1 -1
- flwr/common/record/typeddict.py +12 -0
- flwr/common/retry_invoker.py +30 -11
- flwr/common/telemetry.py +4 -0
- flwr/compat/server/app.py +2 -2
- flwr/proto/appio_pb2.py +25 -17
- flwr/proto/appio_pb2.pyi +46 -2
- flwr/proto/clientappio_pb2.py +3 -11
- flwr/proto/clientappio_pb2.pyi +0 -47
- flwr/proto/clientappio_pb2_grpc.py +19 -20
- flwr/proto/clientappio_pb2_grpc.pyi +10 -11
- flwr/proto/control_pb2.py +66 -0
- flwr/proto/{exec_pb2.pyi → control_pb2.pyi} +24 -0
- flwr/proto/{exec_pb2_grpc.py → control_pb2_grpc.py} +88 -54
- flwr/proto/control_pb2_grpc.pyi +106 -0
- flwr/proto/serverappio_pb2.py +2 -2
- flwr/proto/serverappio_pb2_grpc.py +68 -0
- flwr/proto/serverappio_pb2_grpc.pyi +26 -0
- flwr/proto/simulationio_pb2.py +4 -11
- flwr/proto/simulationio_pb2.pyi +0 -58
- flwr/proto/simulationio_pb2_grpc.py +129 -27
- flwr/proto/simulationio_pb2_grpc.pyi +52 -13
- flwr/server/app.py +142 -152
- flwr/server/grid/grpc_grid.py +3 -0
- flwr/server/grid/inmemory_grid.py +1 -0
- flwr/server/serverapp/app.py +157 -146
- flwr/server/superlink/fleet/vce/backend/raybackend.py +3 -1
- flwr/server/superlink/fleet/vce/vce_api.py +6 -6
- flwr/server/superlink/linkstate/in_memory_linkstate.py +34 -0
- flwr/server/superlink/linkstate/linkstate.py +2 -1
- flwr/server/superlink/linkstate/sqlite_linkstate.py +45 -0
- flwr/server/superlink/serverappio/serverappio_grpc.py +1 -1
- flwr/server/superlink/serverappio/serverappio_servicer.py +61 -6
- flwr/server/superlink/simulation/simulationio_servicer.py +97 -21
- flwr/serverapp/__init__.py +12 -0
- flwr/serverapp/exception.py +38 -0
- flwr/serverapp/strategy/__init__.py +64 -0
- flwr/serverapp/strategy/bulyan.py +238 -0
- flwr/serverapp/strategy/dp_adaptive_clipping.py +335 -0
- flwr/serverapp/strategy/dp_fixed_clipping.py +374 -0
- flwr/serverapp/strategy/fedadagrad.py +159 -0
- flwr/serverapp/strategy/fedadam.py +178 -0
- flwr/serverapp/strategy/fedavg.py +320 -0
- flwr/serverapp/strategy/fedavgm.py +198 -0
- flwr/serverapp/strategy/fedmedian.py +105 -0
- flwr/serverapp/strategy/fedopt.py +218 -0
- flwr/serverapp/strategy/fedprox.py +174 -0
- flwr/serverapp/strategy/fedtrimmedavg.py +176 -0
- flwr/serverapp/strategy/fedxgb_bagging.py +117 -0
- flwr/serverapp/strategy/fedxgb_cyclic.py +220 -0
- flwr/serverapp/strategy/fedyogi.py +170 -0
- flwr/serverapp/strategy/krum.py +112 -0
- flwr/serverapp/strategy/multikrum.py +247 -0
- flwr/serverapp/strategy/qfedavg.py +252 -0
- flwr/serverapp/strategy/result.py +105 -0
- flwr/serverapp/strategy/strategy.py +285 -0
- flwr/serverapp/strategy/strategy_utils.py +299 -0
- flwr/simulation/app.py +161 -164
- flwr/simulation/run_simulation.py +25 -30
- flwr/supercore/app_utils.py +58 -0
- flwr/{supernode/scheduler → supercore/cli}/__init__.py +3 -3
- flwr/supercore/cli/flower_superexec.py +166 -0
- flwr/supercore/constant.py +19 -0
- flwr/supercore/{scheduler → corestate}/__init__.py +3 -3
- flwr/supercore/corestate/corestate.py +81 -0
- flwr/supercore/grpc_health/__init__.py +3 -0
- flwr/supercore/grpc_health/health_server.py +53 -0
- flwr/supercore/grpc_health/simple_health_servicer.py +2 -2
- flwr/{superexec → supercore/superexec}/__init__.py +1 -1
- flwr/supercore/superexec/plugin/__init__.py +28 -0
- flwr/{supernode/scheduler/simple_clientapp_scheduler_plugin.py → supercore/superexec/plugin/base_exec_plugin.py} +10 -6
- flwr/supercore/superexec/plugin/clientapp_exec_plugin.py +28 -0
- flwr/supercore/{scheduler/plugin.py → superexec/plugin/exec_plugin.py} +15 -5
- flwr/supercore/superexec/plugin/serverapp_exec_plugin.py +28 -0
- flwr/supercore/superexec/plugin/simulation_exec_plugin.py +28 -0
- flwr/supercore/superexec/run_superexec.py +199 -0
- flwr/superlink/artifact_provider/__init__.py +22 -0
- flwr/superlink/artifact_provider/artifact_provider.py +37 -0
- flwr/superlink/servicer/__init__.py +15 -0
- flwr/superlink/servicer/control/__init__.py +22 -0
- flwr/{superexec/exec_event_log_interceptor.py → superlink/servicer/control/control_event_log_interceptor.py} +7 -7
- flwr/{superexec/exec_grpc.py → superlink/servicer/control/control_grpc.py} +27 -29
- flwr/{superexec/exec_license_interceptor.py → superlink/servicer/control/control_license_interceptor.py} +6 -6
- flwr/{superexec/exec_servicer.py → superlink/servicer/control/control_servicer.py} +127 -31
- flwr/{superexec/exec_user_auth_interceptor.py → superlink/servicer/control/control_user_auth_interceptor.py} +10 -10
- flwr/supernode/cli/flower_supernode.py +3 -0
- flwr/supernode/cli/flwr_clientapp.py +18 -21
- flwr/supernode/nodestate/in_memory_nodestate.py +2 -2
- flwr/supernode/nodestate/nodestate.py +3 -59
- flwr/supernode/runtime/run_clientapp.py +39 -102
- flwr/supernode/servicer/clientappio/clientappio_servicer.py +10 -17
- flwr/supernode/start_client_internal.py +35 -76
- {flwr-1.20.0.dist-info → flwr-1.22.0.dist-info}/METADATA +9 -18
- {flwr-1.20.0.dist-info → flwr-1.22.0.dist-info}/RECORD +176 -128
- {flwr-1.20.0.dist-info → flwr-1.22.0.dist-info}/entry_points.txt +1 -0
- flwr/proto/exec_pb2.py +0 -62
- flwr/proto/exec_pb2_grpc.pyi +0 -93
- flwr/superexec/app.py +0 -45
- flwr/superexec/deployment.py +0 -191
- flwr/superexec/executor.py +0 -100
- flwr/superexec/simulation.py +0 -129
- {flwr-1.20.0.dist-info → flwr-1.22.0.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
# Copyright 2025 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
|
+
"""Flower SuperExec."""
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
import time
|
|
19
|
+
from logging import WARN
|
|
20
|
+
from typing import Any, Optional, Union
|
|
21
|
+
|
|
22
|
+
from flwr.common.config import get_flwr_dir
|
|
23
|
+
from flwr.common.exit import ExitCode, flwr_exit, register_signal_handlers
|
|
24
|
+
from flwr.common.grpc import create_channel, on_channel_state_change
|
|
25
|
+
from flwr.common.logger import log
|
|
26
|
+
from flwr.common.retry_invoker import _make_simple_grpc_retry_invoker, _wrap_stub
|
|
27
|
+
from flwr.common.serde import run_from_proto
|
|
28
|
+
from flwr.common.telemetry import EventType
|
|
29
|
+
from flwr.common.typing import Run
|
|
30
|
+
from flwr.proto.appio_pb2 import ( # pylint: disable=E0611
|
|
31
|
+
ListAppsToLaunchRequest,
|
|
32
|
+
RequestTokenRequest,
|
|
33
|
+
)
|
|
34
|
+
from flwr.proto.clientappio_pb2_grpc import ClientAppIoStub
|
|
35
|
+
from flwr.proto.run_pb2 import GetRunRequest # pylint: disable=E0611
|
|
36
|
+
from flwr.proto.serverappio_pb2_grpc import ServerAppIoStub
|
|
37
|
+
from flwr.proto.simulationio_pb2_grpc import SimulationIoStub
|
|
38
|
+
from flwr.supercore.app_utils import start_parent_process_monitor
|
|
39
|
+
from flwr.supercore.grpc_health import run_health_server_grpc_no_tls
|
|
40
|
+
|
|
41
|
+
from .plugin import ExecPlugin
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def run_superexec( # pylint: disable=R0913,R0914,R0917
|
|
45
|
+
plugin_class: type[ExecPlugin],
|
|
46
|
+
stub_class: Union[
|
|
47
|
+
type[ClientAppIoStub], type[ServerAppIoStub], type[SimulationIoStub]
|
|
48
|
+
],
|
|
49
|
+
appio_api_address: str,
|
|
50
|
+
plugin_config: Optional[dict[str, Any]] = None,
|
|
51
|
+
flwr_dir: Optional[str] = None,
|
|
52
|
+
parent_pid: Optional[int] = None,
|
|
53
|
+
health_server_address: Optional[str] = None,
|
|
54
|
+
) -> None:
|
|
55
|
+
"""Run Flower SuperExec.
|
|
56
|
+
|
|
57
|
+
Parameters
|
|
58
|
+
----------
|
|
59
|
+
plugin_class : type[ExecPlugin]
|
|
60
|
+
The class of the SuperExec plugin to use.
|
|
61
|
+
stub_class : type[ClientAppIoStub]
|
|
62
|
+
The gRPC stub class for the AppIO API.
|
|
63
|
+
appio_api_address : str
|
|
64
|
+
The address of the AppIO API.
|
|
65
|
+
plugin_config : Optional[dict[str, Any]] (default: None)
|
|
66
|
+
The configuration dictionary for the plugin. If `None`, the plugin will use
|
|
67
|
+
its default configuration.
|
|
68
|
+
flwr_dir : Optional[str] (default: None)
|
|
69
|
+
The Flower directory.
|
|
70
|
+
parent_pid : Optional[int] (default: None)
|
|
71
|
+
The PID of the parent process. If provided, the SuperExec will terminate
|
|
72
|
+
when the parent process exits.
|
|
73
|
+
health_server_address : Optional[str] (default: None)
|
|
74
|
+
The address of the health server. If `None` is provided, the health server will
|
|
75
|
+
NOT be started.
|
|
76
|
+
"""
|
|
77
|
+
# Start monitoring the parent process if a PID is provided
|
|
78
|
+
if parent_pid is not None:
|
|
79
|
+
start_parent_process_monitor(parent_pid)
|
|
80
|
+
|
|
81
|
+
# Launch gRPC health server
|
|
82
|
+
grpc_servers = []
|
|
83
|
+
if health_server_address is not None:
|
|
84
|
+
health_server = run_health_server_grpc_no_tls(health_server_address)
|
|
85
|
+
grpc_servers.append(health_server)
|
|
86
|
+
|
|
87
|
+
# Create the channel to the AppIO API
|
|
88
|
+
# No TLS support for now, so insecure connection
|
|
89
|
+
channel = create_channel(
|
|
90
|
+
server_address=appio_api_address,
|
|
91
|
+
insecure=True,
|
|
92
|
+
root_certificates=None,
|
|
93
|
+
)
|
|
94
|
+
channel.subscribe(on_channel_state_change)
|
|
95
|
+
|
|
96
|
+
# Register exit handlers to close the channel on exit
|
|
97
|
+
register_signal_handlers(
|
|
98
|
+
event_type=EventType.RUN_SUPEREXEC_LEAVE,
|
|
99
|
+
exit_message="SuperExec terminated gracefully.",
|
|
100
|
+
grpc_servers=grpc_servers,
|
|
101
|
+
exit_handlers=[lambda: channel.close()], # pylint: disable=W0108
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
# Create the gRPC stub for the AppIO API
|
|
105
|
+
stub = stub_class(channel)
|
|
106
|
+
_wrap_stub(stub, _make_simple_grpc_retry_invoker())
|
|
107
|
+
|
|
108
|
+
def get_run(run_id: int) -> Run:
|
|
109
|
+
_req = GetRunRequest(run_id=run_id)
|
|
110
|
+
_res = stub.GetRun(_req)
|
|
111
|
+
return run_from_proto(_res.run)
|
|
112
|
+
|
|
113
|
+
# Create the SuperExec plugin instance
|
|
114
|
+
plugin = plugin_class(
|
|
115
|
+
appio_api_address=appio_api_address,
|
|
116
|
+
flwr_dir=str(get_flwr_dir(flwr_dir)),
|
|
117
|
+
get_run=get_run,
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
# Load plugin configuration from file if provided
|
|
121
|
+
try:
|
|
122
|
+
if plugin_config is not None:
|
|
123
|
+
plugin.load_config(plugin_config)
|
|
124
|
+
except (KeyError, ValueError) as e:
|
|
125
|
+
flwr_exit(
|
|
126
|
+
code=ExitCode.SUPEREXEC_INVALID_PLUGIN_CONFIG,
|
|
127
|
+
message=f"Invalid plugin config: {e!r}",
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
# Start the main loop
|
|
131
|
+
try:
|
|
132
|
+
while True:
|
|
133
|
+
# Fetch suitable run IDs
|
|
134
|
+
ls_req = ListAppsToLaunchRequest()
|
|
135
|
+
ls_res = stub.ListAppsToLaunch(ls_req)
|
|
136
|
+
|
|
137
|
+
# Allow the plugin to select a run ID
|
|
138
|
+
run_id = None
|
|
139
|
+
if ls_res.run_ids:
|
|
140
|
+
run_id = plugin.select_run_id(candidate_run_ids=ls_res.run_ids)
|
|
141
|
+
|
|
142
|
+
# Apply for a token if a run ID was selected
|
|
143
|
+
if run_id is not None:
|
|
144
|
+
tk_req = RequestTokenRequest(run_id=run_id)
|
|
145
|
+
tk_res = stub.RequestToken(tk_req)
|
|
146
|
+
|
|
147
|
+
# Launch the app if a token was granted; do nothing if not
|
|
148
|
+
if tk_res.token:
|
|
149
|
+
plugin.launch_app(token=tk_res.token, run_id=run_id)
|
|
150
|
+
|
|
151
|
+
# Sleep for a while before checking again
|
|
152
|
+
time.sleep(1)
|
|
153
|
+
finally:
|
|
154
|
+
channel.close()
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def run_with_deprecation_warning( # pylint: disable=R0913, R0917
|
|
158
|
+
cmd: str,
|
|
159
|
+
plugin_type: str,
|
|
160
|
+
plugin_class: type[ExecPlugin],
|
|
161
|
+
stub_class: Union[
|
|
162
|
+
type[ClientAppIoStub], type[ServerAppIoStub], type[SimulationIoStub]
|
|
163
|
+
],
|
|
164
|
+
appio_api_address: str,
|
|
165
|
+
flwr_dir: Optional[str],
|
|
166
|
+
parent_pid: Optional[int],
|
|
167
|
+
warn_run_once: bool,
|
|
168
|
+
) -> None:
|
|
169
|
+
"""Log a deprecation warning and run the equivalent `flower-superexec` command.
|
|
170
|
+
|
|
171
|
+
Used for legacy long-running `flwr-*` commands (i.e., without `--token`) that will
|
|
172
|
+
be removed in favor of `flower-superexec`.
|
|
173
|
+
"""
|
|
174
|
+
log(
|
|
175
|
+
WARN,
|
|
176
|
+
"Directly executing `%s` is DEPRECATED and will be prohibited "
|
|
177
|
+
"in a future release. Please use `flower-superexec` instead.",
|
|
178
|
+
cmd,
|
|
179
|
+
)
|
|
180
|
+
log(WARN, "For now, the following command is being run automatically:")
|
|
181
|
+
new_cmd = f"flower-superexec --insecure --plugin-type {plugin_type} "
|
|
182
|
+
new_cmd += f"--appio-api-address {appio_api_address} "
|
|
183
|
+
if flwr_dir is not None:
|
|
184
|
+
new_cmd += f"--flwr-dir {flwr_dir} "
|
|
185
|
+
if parent_pid is not None:
|
|
186
|
+
new_cmd += f"--parent-pid {parent_pid}"
|
|
187
|
+
log(WARN, new_cmd)
|
|
188
|
+
|
|
189
|
+
# Warn about unsupported `--run-once` flag
|
|
190
|
+
if warn_run_once:
|
|
191
|
+
log(WARN, "`flower-superexec` does not support the `--run-once` flag.")
|
|
192
|
+
|
|
193
|
+
run_superexec(
|
|
194
|
+
plugin_class=plugin_class,
|
|
195
|
+
stub_class=stub_class,
|
|
196
|
+
appio_api_address=appio_api_address,
|
|
197
|
+
flwr_dir=flwr_dir,
|
|
198
|
+
parent_pid=parent_pid,
|
|
199
|
+
)
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Copyright 2025 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
|
+
"""ArtifactProvider for SuperLink."""
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
from .artifact_provider import ArtifactProvider
|
|
19
|
+
|
|
20
|
+
__all__ = [
|
|
21
|
+
"ArtifactProvider",
|
|
22
|
+
]
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Copyright 2025 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
|
+
"""Abstract base class for ArtifactProvider."""
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
from abc import ABC, abstractmethod
|
|
19
|
+
from typing import Optional
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class ArtifactProvider(ABC):
|
|
23
|
+
"""ArtifactProvider interface for providing artifact download links."""
|
|
24
|
+
|
|
25
|
+
@abstractmethod
|
|
26
|
+
def get_url(self, run_id: int) -> Optional[str]:
|
|
27
|
+
"""Return the artifact download link for the given run ID."""
|
|
28
|
+
|
|
29
|
+
@property
|
|
30
|
+
@abstractmethod
|
|
31
|
+
def output_dir(self) -> str:
|
|
32
|
+
"""Permanent storage directory."""
|
|
33
|
+
|
|
34
|
+
@property
|
|
35
|
+
@abstractmethod
|
|
36
|
+
def tmp_dir(self) -> str:
|
|
37
|
+
"""Temporary storage directory."""
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Copyright 2025 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
|
+
"""Flower SuperLink servicers."""
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Copyright 2025 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
|
+
"""Control API Servicer."""
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
from .control_grpc import run_control_api_grpc
|
|
19
|
+
|
|
20
|
+
__all__ = [
|
|
21
|
+
"run_control_api_grpc",
|
|
22
|
+
]
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
# ==============================================================================
|
|
15
|
-
"""Flower
|
|
15
|
+
"""Flower Control API event log interceptor."""
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
from collections.abc import Iterator
|
|
@@ -24,11 +24,11 @@ from google.protobuf.message import Message as GrpcMessage
|
|
|
24
24
|
from flwr.common.event_log_plugin.event_log_plugin import EventLogWriterPlugin
|
|
25
25
|
from flwr.common.typing import LogEntry
|
|
26
26
|
|
|
27
|
-
from .
|
|
27
|
+
from .control_user_auth_interceptor import shared_account_info
|
|
28
28
|
|
|
29
29
|
|
|
30
|
-
class
|
|
31
|
-
"""
|
|
30
|
+
class ControlEventLogInterceptor(grpc.ServerInterceptor): # type: ignore
|
|
31
|
+
"""Control API interceptor for logging events."""
|
|
32
32
|
|
|
33
33
|
def __init__(self, log_plugin: EventLogWriterPlugin) -> None:
|
|
34
34
|
self.log_plugin = log_plugin
|
|
@@ -44,12 +44,12 @@ class ExecEventLogInterceptor(grpc.ServerInterceptor): # type: ignore
|
|
|
44
44
|
Continue RPC call if event logger is enabled on the SuperLink, else, terminate
|
|
45
45
|
RPC call by setting context to abort.
|
|
46
46
|
"""
|
|
47
|
-
# Only apply to
|
|
48
|
-
if not handler_call_details.method.startswith("/flwr.proto.
|
|
47
|
+
# Only apply to Control service
|
|
48
|
+
if not handler_call_details.method.startswith("/flwr.proto.Control/"):
|
|
49
49
|
return continuation(handler_call_details)
|
|
50
50
|
|
|
51
51
|
# One of the method handlers in
|
|
52
|
-
# `flwr.
|
|
52
|
+
# `flwr.superlink.servicer.control.ControlServicer`
|
|
53
53
|
method_handler: grpc.RpcMethodHandler = continuation(handler_call_details)
|
|
54
54
|
method_name: str = handler_call_details.method
|
|
55
55
|
return self._generic_event_log_unary_method_handler(method_handler, method_name)
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
# ==============================================================================
|
|
15
|
-
"""
|
|
15
|
+
"""Control API server."""
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
from logging import INFO
|
|
@@ -21,23 +21,22 @@ from typing import Optional
|
|
|
21
21
|
import grpc
|
|
22
22
|
|
|
23
23
|
from flwr.common import GRPC_MAX_MESSAGE_LENGTH
|
|
24
|
-
from flwr.common.auth_plugin import
|
|
24
|
+
from flwr.common.auth_plugin import ControlAuthPlugin, ControlAuthzPlugin
|
|
25
25
|
from flwr.common.event_log_plugin import EventLogWriterPlugin
|
|
26
26
|
from flwr.common.exit import ExitCode, flwr_exit
|
|
27
27
|
from flwr.common.grpc import generic_create_grpc_server
|
|
28
28
|
from flwr.common.logger import log
|
|
29
|
-
from flwr.
|
|
30
|
-
from flwr.proto.exec_pb2_grpc import add_ExecServicer_to_server
|
|
29
|
+
from flwr.proto.control_pb2_grpc import add_ControlServicer_to_server
|
|
31
30
|
from flwr.server.superlink.linkstate import LinkStateFactory
|
|
32
31
|
from flwr.supercore.ffs import FfsFactory
|
|
33
32
|
from flwr.supercore.license_plugin import LicensePlugin
|
|
34
33
|
from flwr.supercore.object_store import ObjectStoreFactory
|
|
35
|
-
from flwr.
|
|
36
|
-
from flwr.superexec.exec_license_interceptor import ExecLicenseInterceptor
|
|
37
|
-
from flwr.superexec.exec_user_auth_interceptor import ExecUserAuthInterceptor
|
|
34
|
+
from flwr.superlink.artifact_provider import ArtifactProvider
|
|
38
35
|
|
|
39
|
-
from .
|
|
40
|
-
from .
|
|
36
|
+
from .control_event_log_interceptor import ControlEventLogInterceptor
|
|
37
|
+
from .control_license_interceptor import ControlLicenseInterceptor
|
|
38
|
+
from .control_servicer import ControlServicer
|
|
39
|
+
from .control_user_auth_interceptor import ControlUserAuthInterceptor
|
|
41
40
|
|
|
42
41
|
try:
|
|
43
42
|
from flwr.ee import get_license_plugin
|
|
@@ -48,44 +47,43 @@ except ImportError:
|
|
|
48
47
|
|
|
49
48
|
|
|
50
49
|
# pylint: disable-next=too-many-arguments,too-many-positional-arguments,too-many-locals
|
|
51
|
-
def
|
|
50
|
+
def run_control_api_grpc(
|
|
52
51
|
address: str,
|
|
53
|
-
executor: Executor,
|
|
54
52
|
state_factory: LinkStateFactory,
|
|
55
53
|
ffs_factory: FfsFactory,
|
|
56
54
|
objectstore_factory: ObjectStoreFactory,
|
|
57
55
|
certificates: Optional[tuple[bytes, bytes, bytes]],
|
|
58
|
-
|
|
59
|
-
auth_plugin: Optional[
|
|
60
|
-
authz_plugin: Optional[
|
|
56
|
+
is_simulation: bool,
|
|
57
|
+
auth_plugin: Optional[ControlAuthPlugin] = None,
|
|
58
|
+
authz_plugin: Optional[ControlAuthzPlugin] = None,
|
|
61
59
|
event_log_plugin: Optional[EventLogWriterPlugin] = None,
|
|
60
|
+
artifact_provider: Optional[ArtifactProvider] = None,
|
|
62
61
|
) -> grpc.Server:
|
|
63
|
-
"""Run
|
|
64
|
-
executor.set_config(config)
|
|
65
|
-
|
|
62
|
+
"""Run Control API (gRPC, request-response)."""
|
|
66
63
|
license_plugin: Optional[LicensePlugin] = get_license_plugin()
|
|
67
64
|
if license_plugin and not license_plugin.check_license():
|
|
68
65
|
flwr_exit(ExitCode.SUPERLINK_LICENSE_INVALID)
|
|
69
66
|
|
|
70
|
-
|
|
67
|
+
control_servicer: grpc.Server = ControlServicer(
|
|
71
68
|
linkstate_factory=state_factory,
|
|
72
69
|
ffs_factory=ffs_factory,
|
|
73
70
|
objectstore_factory=objectstore_factory,
|
|
74
|
-
|
|
71
|
+
is_simulation=is_simulation,
|
|
75
72
|
auth_plugin=auth_plugin,
|
|
73
|
+
artifact_provider=artifact_provider,
|
|
76
74
|
)
|
|
77
75
|
interceptors: list[grpc.ServerInterceptor] = []
|
|
78
76
|
if license_plugin is not None:
|
|
79
|
-
interceptors.append(
|
|
77
|
+
interceptors.append(ControlLicenseInterceptor(license_plugin))
|
|
80
78
|
if auth_plugin is not None and authz_plugin is not None:
|
|
81
|
-
interceptors.append(
|
|
79
|
+
interceptors.append(ControlUserAuthInterceptor(auth_plugin, authz_plugin))
|
|
82
80
|
# Event log interceptor must be added after user auth interceptor
|
|
83
81
|
if event_log_plugin is not None:
|
|
84
|
-
interceptors.append(
|
|
82
|
+
interceptors.append(ControlEventLogInterceptor(event_log_plugin))
|
|
85
83
|
log(INFO, "Flower event logging enabled")
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
servicer_and_add_fn=(
|
|
84
|
+
control_add_servicer_to_server_fn = add_ControlServicer_to_server
|
|
85
|
+
control_grpc_server = generic_create_grpc_server(
|
|
86
|
+
servicer_and_add_fn=(control_servicer, control_add_servicer_to_server_fn),
|
|
89
87
|
server_address=address,
|
|
90
88
|
max_message_length=GRPC_MAX_MESSAGE_LENGTH,
|
|
91
89
|
certificates=certificates,
|
|
@@ -93,14 +91,14 @@ def run_exec_api_grpc(
|
|
|
93
91
|
)
|
|
94
92
|
|
|
95
93
|
if auth_plugin is None:
|
|
96
|
-
log(INFO, "Flower Deployment
|
|
94
|
+
log(INFO, "Flower Deployment Runtime: Starting Control API on %s", address)
|
|
97
95
|
else:
|
|
98
96
|
log(
|
|
99
97
|
INFO,
|
|
100
|
-
"Flower Deployment
|
|
98
|
+
"Flower Deployment Runtime: Starting Control API with user "
|
|
101
99
|
"authentication on %s",
|
|
102
100
|
address,
|
|
103
101
|
)
|
|
104
|
-
|
|
102
|
+
control_grpc_server.start()
|
|
105
103
|
|
|
106
|
-
return
|
|
104
|
+
return control_grpc_server
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
# ==============================================================================
|
|
15
|
-
"""Flower
|
|
15
|
+
"""Flower Control API license interceptor."""
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
from collections.abc import Iterator
|
|
@@ -24,8 +24,8 @@ from google.protobuf.message import Message as GrpcMessage
|
|
|
24
24
|
from flwr.supercore.license_plugin import LicensePlugin
|
|
25
25
|
|
|
26
26
|
|
|
27
|
-
class
|
|
28
|
-
"""
|
|
27
|
+
class ControlLicenseInterceptor(grpc.ServerInterceptor): # type: ignore
|
|
28
|
+
"""Control API interceptor for license checking."""
|
|
29
29
|
|
|
30
30
|
def __init__(self, license_plugin: LicensePlugin) -> None:
|
|
31
31
|
"""Initialize the interceptor with a license plugin."""
|
|
@@ -42,12 +42,12 @@ class ExecLicenseInterceptor(grpc.ServerInterceptor): # type: ignore
|
|
|
42
42
|
Continue RPC call if license check is enabled and passes, else, terminate RPC
|
|
43
43
|
call by setting context to abort.
|
|
44
44
|
"""
|
|
45
|
-
# Only apply to
|
|
46
|
-
if not handler_call_details.method.startswith("/flwr.proto.
|
|
45
|
+
# Only apply to Control service
|
|
46
|
+
if not handler_call_details.method.startswith("/flwr.proto.Control/"):
|
|
47
47
|
return continuation(handler_call_details)
|
|
48
48
|
|
|
49
49
|
# One of the method handlers in
|
|
50
|
-
# `flwr.
|
|
50
|
+
# `flwr.superlink.servicer.control.ControlServicer`
|
|
51
51
|
method_handler: grpc.RpcMethodHandler = continuation(handler_call_details)
|
|
52
52
|
return self._generic_license_unary_method_handler(method_handler)
|
|
53
53
|
|