flwr 1.19.0__py3-none-any.whl → 1.21.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/auth_plugin/oidc_cli_plugin.py +4 -4
- flwr/cli/build.py +15 -5
- 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 +23 -4
- flwr/cli/new/templates/app/README.flowertune.md.tpl +2 -0
- flwr/cli/new/templates/app/README.md.tpl +5 -0
- flwr/cli/new/templates/app/code/__init__.pytorch_msg_api.py.tpl +1 -0
- flwr/cli/new/templates/app/code/client.pytorch_msg_api.py.tpl +80 -0
- flwr/cli/new/templates/app/code/server.pytorch_msg_api.py.tpl +41 -0
- flwr/cli/new/templates/app/code/task.pytorch_msg_api.py.tpl +98 -0
- flwr/cli/new/templates/app/pyproject.baseline.toml.tpl +14 -3
- flwr/cli/new/templates/app/pyproject.flowertune.toml.tpl +13 -1
- flwr/cli/new/templates/app/pyproject.huggingface.toml.tpl +21 -2
- flwr/cli/new/templates/app/pyproject.jax.toml.tpl +18 -1
- flwr/cli/new/templates/app/pyproject.mlx.toml.tpl +19 -2
- flwr/cli/new/templates/app/pyproject.numpy.toml.tpl +18 -1
- flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl +20 -3
- flwr/cli/new/templates/app/pyproject.pytorch_msg_api.toml.tpl +53 -0
- flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl +18 -1
- flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl +18 -1
- flwr/cli/run/run.py +53 -50
- flwr/cli/stop.py +7 -4
- flwr/cli/utils.py +29 -11
- flwr/client/grpc_adapter_client/connection.py +11 -4
- flwr/client/grpc_rere_client/connection.py +93 -129
- flwr/client/rest_client/connection.py +134 -164
- flwr/clientapp/__init__.py +10 -0
- flwr/clientapp/mod/__init__.py +26 -0
- flwr/clientapp/mod/centraldp_mods.py +132 -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 -5
- 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 +42 -8
- flwr/common/exit/exit_handler.py +62 -0
- flwr/common/{exit_handlers.py → exit/signal_handler.py} +20 -37
- flwr/common/grpc.py +1 -1
- flwr/common/{inflatable_grpc_utils.py → inflatable_protobuf_utils.py} +52 -10
- flwr/common/inflatable_utils.py +191 -24
- flwr/common/logger.py +1 -1
- flwr/common/record/array.py +101 -22
- flwr/common/record/arraychunk.py +59 -0
- flwr/common/retry_invoker.py +30 -11
- flwr/common/serde.py +0 -28
- flwr/common/telemetry.py +4 -0
- flwr/compat/client/app.py +14 -31
- flwr/compat/server/app.py +2 -2
- flwr/proto/appio_pb2.py +51 -0
- flwr/proto/appio_pb2.pyi +195 -0
- flwr/proto/appio_pb2_grpc.py +4 -0
- flwr/proto/appio_pb2_grpc.pyi +4 -0
- flwr/proto/clientappio_pb2.py +4 -19
- flwr/proto/clientappio_pb2.pyi +0 -125
- flwr/proto/clientappio_pb2_grpc.py +269 -29
- flwr/proto/clientappio_pb2_grpc.pyi +114 -21
- flwr/proto/control_pb2.py +62 -0
- flwr/proto/{exec_pb2_grpc.py → control_pb2_grpc.py} +54 -54
- flwr/proto/{exec_pb2_grpc.pyi → control_pb2_grpc.pyi} +28 -28
- flwr/proto/fleet_pb2.py +12 -20
- flwr/proto/fleet_pb2.pyi +6 -36
- flwr/proto/serverappio_pb2.py +8 -31
- flwr/proto/serverappio_pb2.pyi +0 -152
- flwr/proto/serverappio_pb2_grpc.py +107 -38
- flwr/proto/serverappio_pb2_grpc.pyi +47 -20
- 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 +130 -153
- flwr/server/fleet_event_log_interceptor.py +4 -0
- flwr/server/grid/grpc_grid.py +94 -54
- flwr/server/grid/inmemory_grid.py +1 -0
- flwr/server/serverapp/app.py +165 -144
- flwr/server/superlink/fleet/grpc_adapter/grpc_adapter_servicer.py +8 -0
- flwr/server/superlink/fleet/grpc_rere/fleet_servicer.py +1 -1
- flwr/server/superlink/fleet/grpc_rere/server_interceptor.py +2 -5
- flwr/server/superlink/fleet/message_handler/message_handler.py +10 -16
- flwr/server/superlink/fleet/rest_rere/rest_api.py +1 -2
- 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 +2 -2
- flwr/server/superlink/serverappio/serverappio_servicer.py +95 -48
- flwr/server/superlink/simulation/simulationio_grpc.py +1 -1
- flwr/server/superlink/simulation/simulationio_servicer.py +98 -22
- flwr/server/superlink/utils.py +0 -35
- flwr/serverapp/__init__.py +12 -0
- flwr/serverapp/dp_fixed_clipping.py +352 -0
- flwr/serverapp/exception.py +38 -0
- flwr/serverapp/strategy/__init__.py +38 -0
- flwr/serverapp/strategy/dp_fixed_clipping.py +352 -0
- flwr/serverapp/strategy/fedadagrad.py +162 -0
- flwr/serverapp/strategy/fedadam.py +181 -0
- flwr/serverapp/strategy/fedavg.py +295 -0
- flwr/serverapp/strategy/fedopt.py +218 -0
- flwr/serverapp/strategy/fedyogi.py +173 -0
- flwr/serverapp/strategy/result.py +105 -0
- flwr/serverapp/strategy/strategy.py +285 -0
- flwr/serverapp/strategy/strategy_utils.py +251 -0
- flwr/serverapp/strategy/strategy_utils_tests.py +304 -0
- flwr/simulation/app.py +159 -154
- flwr/simulation/run_simulation.py +17 -0
- flwr/supercore/app_utils.py +58 -0
- flwr/supercore/cli/__init__.py +22 -0
- flwr/supercore/cli/flower_superexec.py +141 -0
- flwr/supercore/corestate/__init__.py +22 -0
- flwr/supercore/corestate/corestate.py +81 -0
- flwr/{server/superlink → supercore}/ffs/disk_ffs.py +1 -1
- flwr/supercore/grpc_health/__init__.py +25 -0
- flwr/supercore/grpc_health/health_server.py +53 -0
- flwr/supercore/grpc_health/simple_health_servicer.py +38 -0
- flwr/supercore/license_plugin/__init__.py +22 -0
- flwr/supercore/license_plugin/license_plugin.py +26 -0
- flwr/supercore/object_store/in_memory_object_store.py +31 -31
- flwr/supercore/object_store/object_store.py +20 -42
- flwr/supercore/object_store/utils.py +43 -0
- flwr/{superexec → supercore/superexec}/__init__.py +1 -1
- flwr/supercore/superexec/plugin/__init__.py +28 -0
- flwr/supercore/superexec/plugin/base_exec_plugin.py +53 -0
- flwr/supercore/superexec/plugin/clientapp_exec_plugin.py +28 -0
- flwr/supercore/superexec/plugin/exec_plugin.py +71 -0
- 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 +185 -0
- flwr/supercore/utils.py +32 -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} +9 -5
- flwr/{superexec/exec_grpc.py → superlink/servicer/control/control_grpc.py} +39 -28
- flwr/superlink/servicer/control/control_license_interceptor.py +82 -0
- flwr/{superexec/exec_servicer.py → superlink/servicer/control/control_servicer.py} +79 -31
- flwr/{superexec/exec_user_auth_interceptor.py → superlink/servicer/control/control_user_auth_interceptor.py} +18 -10
- flwr/supernode/cli/flower_supernode.py +3 -7
- flwr/supernode/cli/flwr_clientapp.py +20 -16
- flwr/supernode/nodestate/in_memory_nodestate.py +13 -4
- flwr/supernode/nodestate/nodestate.py +3 -44
- flwr/supernode/runtime/run_clientapp.py +129 -115
- flwr/supernode/servicer/clientappio/__init__.py +1 -3
- flwr/supernode/servicer/clientappio/clientappio_servicer.py +217 -165
- flwr/supernode/start_client_internal.py +205 -148
- {flwr-1.19.0.dist-info → flwr-1.21.0.dist-info}/METADATA +5 -3
- {flwr-1.19.0.dist-info → flwr-1.21.0.dist-info}/RECORD +161 -117
- {flwr-1.19.0.dist-info → flwr-1.21.0.dist-info}/entry_points.txt +1 -0
- flwr/common/inflatable_rest_utils.py +0 -99
- flwr/proto/exec_pb2.py +0 -62
- flwr/superexec/app.py +0 -45
- flwr/superexec/deployment.py +0 -192
- flwr/superexec/executor.py +0 -100
- flwr/superexec/simulation.py +0 -130
- /flwr/proto/{exec_pb2.pyi → control_pb2.pyi} +0 -0
- /flwr/{server/superlink → supercore}/ffs/__init__.py +0 -0
- /flwr/{server/superlink → supercore}/ffs/ffs.py +0 -0
- /flwr/{server/superlink → supercore}/ffs/ffs_factory.py +0 -0
- {flwr-1.19.0.dist-info → flwr-1.21.0.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,71 @@
|
|
|
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 ExecPlugin."""
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
from abc import ABC, abstractmethod
|
|
19
|
+
from collections.abc import Sequence
|
|
20
|
+
from typing import Callable, Optional
|
|
21
|
+
|
|
22
|
+
from flwr.common.typing import Run
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class ExecPlugin(ABC):
|
|
26
|
+
"""Abstract base class for SuperExec plugins."""
|
|
27
|
+
|
|
28
|
+
def __init__(
|
|
29
|
+
self,
|
|
30
|
+
appio_api_address: str,
|
|
31
|
+
flwr_dir: str,
|
|
32
|
+
get_run: Callable[[int], Run],
|
|
33
|
+
) -> None:
|
|
34
|
+
self.appio_api_address = appio_api_address
|
|
35
|
+
self.flwr_dir = flwr_dir
|
|
36
|
+
self.get_run = get_run
|
|
37
|
+
|
|
38
|
+
@abstractmethod
|
|
39
|
+
def select_run_id(self, candidate_run_ids: Sequence[int]) -> Optional[int]:
|
|
40
|
+
"""Select a run ID to execute from a sequence of candidates.
|
|
41
|
+
|
|
42
|
+
A candidate run ID is one that has at least one pending message and is
|
|
43
|
+
not currently in progress (i.e., not associated with a token).
|
|
44
|
+
|
|
45
|
+
Parameters
|
|
46
|
+
----------
|
|
47
|
+
candidate_run_ids : Sequence[int]
|
|
48
|
+
A sequence of candidate run IDs to choose from.
|
|
49
|
+
|
|
50
|
+
Returns
|
|
51
|
+
-------
|
|
52
|
+
Optional[int]
|
|
53
|
+
The selected run ID, or None if no suitable candidate is found.
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
@abstractmethod
|
|
57
|
+
def launch_app(self, token: str, run_id: int) -> None:
|
|
58
|
+
"""Launch the application associated with a given run ID and token.
|
|
59
|
+
|
|
60
|
+
This method starts the application process using the given `token`.
|
|
61
|
+
The `run_id` is used solely for bookkeeping purposes, allowing any
|
|
62
|
+
plugin implementation to associate this launch with a specific run.
|
|
63
|
+
|
|
64
|
+
Parameters
|
|
65
|
+
----------
|
|
66
|
+
token : str
|
|
67
|
+
The token required to run the application.
|
|
68
|
+
run_id : int
|
|
69
|
+
The ID of the run associated with the token, used for tracking or
|
|
70
|
+
logging purposes.
|
|
71
|
+
"""
|
|
@@ -0,0 +1,28 @@
|
|
|
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
|
+
"""Simple Flower SuperExec plugin for ServerApp."""
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
from .base_exec_plugin import BaseExecPlugin
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class ServerAppExecPlugin(BaseExecPlugin):
|
|
22
|
+
"""Simple Flower SuperExec plugin for ServerApp.
|
|
23
|
+
|
|
24
|
+
The plugin always selects the first candidate run ID.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
command = "flwr-serverapp"
|
|
28
|
+
appio_api_address_arg = "--serverappio-api-address"
|
|
@@ -0,0 +1,28 @@
|
|
|
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
|
+
"""Simple Flower SuperExec plugin for simulation processes."""
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
from .base_exec_plugin import BaseExecPlugin
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class SimulationExecPlugin(BaseExecPlugin):
|
|
22
|
+
"""Simple Flower SuperExec plugin for simulation processes.
|
|
23
|
+
|
|
24
|
+
The plugin always selects the first candidate run ID.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
command = "flwr-simulation"
|
|
28
|
+
appio_api_address_arg = "--simulationio-api-address"
|
|
@@ -0,0 +1,185 @@
|
|
|
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 Optional, Union
|
|
21
|
+
|
|
22
|
+
from flwr.common.config import get_flwr_dir
|
|
23
|
+
from flwr.common.exit import 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
|
+
flwr_dir: Optional[str] = None,
|
|
51
|
+
parent_pid: Optional[int] = None,
|
|
52
|
+
health_server_address: Optional[str] = None,
|
|
53
|
+
) -> None:
|
|
54
|
+
"""Run Flower SuperExec.
|
|
55
|
+
|
|
56
|
+
Parameters
|
|
57
|
+
----------
|
|
58
|
+
plugin_class : type[ExecPlugin]
|
|
59
|
+
The class of the SuperExec plugin to use.
|
|
60
|
+
stub_class : type[ClientAppIoStub]
|
|
61
|
+
The gRPC stub class for the AppIO API.
|
|
62
|
+
appio_api_address : str
|
|
63
|
+
The address of the AppIO API.
|
|
64
|
+
flwr_dir : Optional[str] (default: None)
|
|
65
|
+
The Flower directory.
|
|
66
|
+
parent_pid : Optional[int] (default: None)
|
|
67
|
+
The PID of the parent process. If provided, the SuperExec will terminate
|
|
68
|
+
when the parent process exits.
|
|
69
|
+
health_server_address : Optional[str] (default: None)
|
|
70
|
+
The address of the health server. If `None` is provided, the health server will
|
|
71
|
+
NOT be started.
|
|
72
|
+
"""
|
|
73
|
+
# Start monitoring the parent process if a PID is provided
|
|
74
|
+
if parent_pid is not None:
|
|
75
|
+
start_parent_process_monitor(parent_pid)
|
|
76
|
+
|
|
77
|
+
# Launch gRPC health server
|
|
78
|
+
grpc_servers = []
|
|
79
|
+
if health_server_address is not None:
|
|
80
|
+
health_server = run_health_server_grpc_no_tls(health_server_address)
|
|
81
|
+
grpc_servers.append(health_server)
|
|
82
|
+
|
|
83
|
+
# Create the channel to the AppIO API
|
|
84
|
+
# No TLS support for now, so insecure connection
|
|
85
|
+
channel = create_channel(
|
|
86
|
+
server_address=appio_api_address,
|
|
87
|
+
insecure=True,
|
|
88
|
+
root_certificates=None,
|
|
89
|
+
)
|
|
90
|
+
channel.subscribe(on_channel_state_change)
|
|
91
|
+
|
|
92
|
+
# Register exit handlers to close the channel on exit
|
|
93
|
+
register_signal_handlers(
|
|
94
|
+
event_type=EventType.RUN_SUPEREXEC_LEAVE,
|
|
95
|
+
exit_message="SuperExec terminated gracefully.",
|
|
96
|
+
grpc_servers=grpc_servers,
|
|
97
|
+
exit_handlers=[lambda: channel.close()], # pylint: disable=W0108
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
# Create the gRPC stub for the AppIO API
|
|
101
|
+
stub = stub_class(channel)
|
|
102
|
+
_wrap_stub(stub, _make_simple_grpc_retry_invoker())
|
|
103
|
+
|
|
104
|
+
def get_run(run_id: int) -> Run:
|
|
105
|
+
_req = GetRunRequest(run_id=run_id)
|
|
106
|
+
_res = stub.GetRun(_req)
|
|
107
|
+
return run_from_proto(_res.run)
|
|
108
|
+
|
|
109
|
+
# Create the SuperExec plugin instance
|
|
110
|
+
plugin = plugin_class(
|
|
111
|
+
appio_api_address=appio_api_address,
|
|
112
|
+
flwr_dir=str(get_flwr_dir(flwr_dir)),
|
|
113
|
+
get_run=get_run,
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
# Start the main loop
|
|
117
|
+
try:
|
|
118
|
+
while True:
|
|
119
|
+
# Fetch suitable run IDs
|
|
120
|
+
ls_req = ListAppsToLaunchRequest()
|
|
121
|
+
ls_res = stub.ListAppsToLaunch(ls_req)
|
|
122
|
+
|
|
123
|
+
# Allow the plugin to select a run ID
|
|
124
|
+
run_id = None
|
|
125
|
+
if ls_res.run_ids:
|
|
126
|
+
run_id = plugin.select_run_id(candidate_run_ids=ls_res.run_ids)
|
|
127
|
+
|
|
128
|
+
# Apply for a token if a run ID was selected
|
|
129
|
+
if run_id is not None:
|
|
130
|
+
tk_req = RequestTokenRequest(run_id=run_id)
|
|
131
|
+
tk_res = stub.RequestToken(tk_req)
|
|
132
|
+
|
|
133
|
+
# Launch the app if a token was granted; do nothing if not
|
|
134
|
+
if tk_res.token:
|
|
135
|
+
plugin.launch_app(token=tk_res.token, run_id=run_id)
|
|
136
|
+
|
|
137
|
+
# Sleep for a while before checking again
|
|
138
|
+
time.sleep(1)
|
|
139
|
+
finally:
|
|
140
|
+
channel.close()
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def run_with_deprecation_warning( # pylint: disable=R0913, R0917
|
|
144
|
+
cmd: str,
|
|
145
|
+
plugin_type: str,
|
|
146
|
+
plugin_class: type[ExecPlugin],
|
|
147
|
+
stub_class: Union[
|
|
148
|
+
type[ClientAppIoStub], type[ServerAppIoStub], type[SimulationIoStub]
|
|
149
|
+
],
|
|
150
|
+
appio_api_address: str,
|
|
151
|
+
flwr_dir: Optional[str],
|
|
152
|
+
parent_pid: Optional[int],
|
|
153
|
+
warn_run_once: bool,
|
|
154
|
+
) -> None:
|
|
155
|
+
"""Log a deprecation warning and run the equivalent `flower-superexec` command.
|
|
156
|
+
|
|
157
|
+
Used for legacy long-running `flwr-*` commands (i.e., without `--token`) that will
|
|
158
|
+
be removed in favor of `flower-superexec`.
|
|
159
|
+
"""
|
|
160
|
+
log(
|
|
161
|
+
WARN,
|
|
162
|
+
"Directly executing `%s` is DEPRECATED and will be prohibited "
|
|
163
|
+
"in a future release. Please use `flower-superexec` instead.",
|
|
164
|
+
cmd,
|
|
165
|
+
)
|
|
166
|
+
log(WARN, "For now, the following command is being run automatically:")
|
|
167
|
+
new_cmd = f"flower-superexec --insecure --plugin-type {plugin_type} "
|
|
168
|
+
new_cmd += f"--appio-api-address {appio_api_address} "
|
|
169
|
+
if flwr_dir is not None:
|
|
170
|
+
new_cmd += f"--flwr-dir {flwr_dir} "
|
|
171
|
+
if parent_pid is not None:
|
|
172
|
+
new_cmd += f"--parent-pid {parent_pid}"
|
|
173
|
+
log(WARN, new_cmd)
|
|
174
|
+
|
|
175
|
+
# Warn about unsupported `--run-once` flag
|
|
176
|
+
if warn_run_once:
|
|
177
|
+
log(WARN, "`flower-superexec` does not support the `--run-once` flag.")
|
|
178
|
+
|
|
179
|
+
run_superexec(
|
|
180
|
+
plugin_class=plugin_class,
|
|
181
|
+
stub_class=stub_class,
|
|
182
|
+
appio_api_address=appio_api_address,
|
|
183
|
+
flwr_dir=flwr_dir,
|
|
184
|
+
parent_pid=parent_pid,
|
|
185
|
+
)
|
flwr/supercore/utils.py
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
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
|
+
"""Utility functions for the infrastructure."""
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def mask_string(value: str, head: int = 4, tail: int = 4) -> str:
|
|
19
|
+
"""Mask a string by preserving only the head and tail characters.
|
|
20
|
+
|
|
21
|
+
Mask a string for safe display by preserving the head and tail characters,
|
|
22
|
+
and replacing the middle with '...'. Useful for logging tokens, secrets,
|
|
23
|
+
or IDs without exposing sensitive data.
|
|
24
|
+
|
|
25
|
+
Notes
|
|
26
|
+
-----
|
|
27
|
+
If the string is shorter than the combined length of `head` and `tail`,
|
|
28
|
+
the original string is returned unchanged.
|
|
29
|
+
"""
|
|
30
|
+
if len(value) <= head + tail:
|
|
31
|
+
return value
|
|
32
|
+
return f"{value[:head]}...{value[-tail:]}"
|
|
@@ -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,8 +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 Control service
|
|
48
|
+
if not handler_call_details.method.startswith("/flwr.proto.Control/"):
|
|
49
|
+
return continuation(handler_call_details)
|
|
50
|
+
|
|
47
51
|
# One of the method handlers in
|
|
48
|
-
# `flwr.
|
|
52
|
+
# `flwr.superlink.servicer.control.ControlServicer`
|
|
49
53
|
method_handler: grpc.RpcMethodHandler = continuation(handler_call_details)
|
|
50
54
|
method_name: str = handler_call_details.method
|
|
51
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,55 +21,66 @@ 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
|
+
from flwr.common.exit import ExitCode, flwr_exit
|
|
26
27
|
from flwr.common.grpc import generic_create_grpc_server
|
|
27
28
|
from flwr.common.logger import log
|
|
28
|
-
from flwr.
|
|
29
|
-
from flwr.proto.exec_pb2_grpc import add_ExecServicer_to_server
|
|
30
|
-
from flwr.server.superlink.ffs.ffs_factory import FfsFactory
|
|
29
|
+
from flwr.proto.control_pb2_grpc import add_ControlServicer_to_server
|
|
31
30
|
from flwr.server.superlink.linkstate import LinkStateFactory
|
|
31
|
+
from flwr.supercore.ffs import FfsFactory
|
|
32
|
+
from flwr.supercore.license_plugin import LicensePlugin
|
|
32
33
|
from flwr.supercore.object_store import ObjectStoreFactory
|
|
33
|
-
from flwr.superexec.exec_event_log_interceptor import ExecEventLogInterceptor
|
|
34
|
-
from flwr.superexec.exec_user_auth_interceptor import ExecUserAuthInterceptor
|
|
35
34
|
|
|
36
|
-
from .
|
|
37
|
-
from .
|
|
35
|
+
from .control_event_log_interceptor import ControlEventLogInterceptor
|
|
36
|
+
from .control_license_interceptor import ControlLicenseInterceptor
|
|
37
|
+
from .control_servicer import ControlServicer
|
|
38
|
+
from .control_user_auth_interceptor import ControlUserAuthInterceptor
|
|
38
39
|
|
|
40
|
+
try:
|
|
41
|
+
from flwr.ee import get_license_plugin
|
|
42
|
+
except ImportError:
|
|
39
43
|
|
|
40
|
-
|
|
41
|
-
|
|
44
|
+
def get_license_plugin() -> Optional[LicensePlugin]:
|
|
45
|
+
"""Return the license plugin."""
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
# pylint: disable-next=too-many-arguments,too-many-positional-arguments,too-many-locals
|
|
49
|
+
def run_control_api_grpc(
|
|
42
50
|
address: str,
|
|
43
|
-
executor: Executor,
|
|
44
51
|
state_factory: LinkStateFactory,
|
|
45
52
|
ffs_factory: FfsFactory,
|
|
46
53
|
objectstore_factory: ObjectStoreFactory,
|
|
47
54
|
certificates: Optional[tuple[bytes, bytes, bytes]],
|
|
48
|
-
|
|
49
|
-
auth_plugin: Optional[
|
|
50
|
-
authz_plugin: Optional[
|
|
55
|
+
is_simulation: bool,
|
|
56
|
+
auth_plugin: Optional[ControlAuthPlugin] = None,
|
|
57
|
+
authz_plugin: Optional[ControlAuthzPlugin] = None,
|
|
51
58
|
event_log_plugin: Optional[EventLogWriterPlugin] = None,
|
|
52
59
|
) -> grpc.Server:
|
|
53
|
-
"""Run
|
|
54
|
-
|
|
60
|
+
"""Run Control API (gRPC, request-response)."""
|
|
61
|
+
license_plugin: Optional[LicensePlugin] = get_license_plugin()
|
|
62
|
+
if license_plugin and not license_plugin.check_license():
|
|
63
|
+
flwr_exit(ExitCode.SUPERLINK_LICENSE_INVALID)
|
|
55
64
|
|
|
56
|
-
|
|
65
|
+
control_servicer: grpc.Server = ControlServicer(
|
|
57
66
|
linkstate_factory=state_factory,
|
|
58
67
|
ffs_factory=ffs_factory,
|
|
59
68
|
objectstore_factory=objectstore_factory,
|
|
60
|
-
|
|
69
|
+
is_simulation=is_simulation,
|
|
61
70
|
auth_plugin=auth_plugin,
|
|
62
71
|
)
|
|
63
72
|
interceptors: list[grpc.ServerInterceptor] = []
|
|
73
|
+
if license_plugin is not None:
|
|
74
|
+
interceptors.append(ControlLicenseInterceptor(license_plugin))
|
|
64
75
|
if auth_plugin is not None and authz_plugin is not None:
|
|
65
|
-
interceptors.append(
|
|
76
|
+
interceptors.append(ControlUserAuthInterceptor(auth_plugin, authz_plugin))
|
|
66
77
|
# Event log interceptor must be added after user auth interceptor
|
|
67
78
|
if event_log_plugin is not None:
|
|
68
|
-
interceptors.append(
|
|
79
|
+
interceptors.append(ControlEventLogInterceptor(event_log_plugin))
|
|
69
80
|
log(INFO, "Flower event logging enabled")
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
servicer_and_add_fn=(
|
|
81
|
+
control_add_servicer_to_server_fn = add_ControlServicer_to_server
|
|
82
|
+
control_grpc_server = generic_create_grpc_server(
|
|
83
|
+
servicer_and_add_fn=(control_servicer, control_add_servicer_to_server_fn),
|
|
73
84
|
server_address=address,
|
|
74
85
|
max_message_length=GRPC_MAX_MESSAGE_LENGTH,
|
|
75
86
|
certificates=certificates,
|
|
@@ -77,14 +88,14 @@ def run_exec_api_grpc(
|
|
|
77
88
|
)
|
|
78
89
|
|
|
79
90
|
if auth_plugin is None:
|
|
80
|
-
log(INFO, "Flower Deployment
|
|
91
|
+
log(INFO, "Flower Deployment Runtime: Starting Control API on %s", address)
|
|
81
92
|
else:
|
|
82
93
|
log(
|
|
83
94
|
INFO,
|
|
84
|
-
"Flower Deployment
|
|
95
|
+
"Flower Deployment Runtime: Starting Control API with user "
|
|
85
96
|
"authentication on %s",
|
|
86
97
|
address,
|
|
87
98
|
)
|
|
88
|
-
|
|
99
|
+
control_grpc_server.start()
|
|
89
100
|
|
|
90
|
-
return
|
|
101
|
+
return control_grpc_server
|
|
@@ -0,0 +1,82 @@
|
|
|
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 Control API license interceptor."""
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
from collections.abc import Iterator
|
|
19
|
+
from typing import Any, Callable, Union
|
|
20
|
+
|
|
21
|
+
import grpc
|
|
22
|
+
from google.protobuf.message import Message as GrpcMessage
|
|
23
|
+
|
|
24
|
+
from flwr.supercore.license_plugin import LicensePlugin
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class ControlLicenseInterceptor(grpc.ServerInterceptor): # type: ignore
|
|
28
|
+
"""Control API interceptor for license checking."""
|
|
29
|
+
|
|
30
|
+
def __init__(self, license_plugin: LicensePlugin) -> None:
|
|
31
|
+
"""Initialize the interceptor with a license plugin."""
|
|
32
|
+
self.license_plugin = license_plugin
|
|
33
|
+
|
|
34
|
+
def intercept_service(
|
|
35
|
+
self,
|
|
36
|
+
continuation: Callable[[Any], Any],
|
|
37
|
+
handler_call_details: grpc.HandlerCallDetails,
|
|
38
|
+
) -> grpc.RpcMethodHandler:
|
|
39
|
+
"""Flower server interceptor license logic.
|
|
40
|
+
|
|
41
|
+
Intercept all unary-unary/unary-stream calls from users and check the license.
|
|
42
|
+
Continue RPC call if license check is enabled and passes, else, terminate RPC
|
|
43
|
+
call by setting context to abort.
|
|
44
|
+
"""
|
|
45
|
+
# Only apply to Control service
|
|
46
|
+
if not handler_call_details.method.startswith("/flwr.proto.Control/"):
|
|
47
|
+
return continuation(handler_call_details)
|
|
48
|
+
|
|
49
|
+
# One of the method handlers in
|
|
50
|
+
# `flwr.superlink.servicer.control.ControlServicer`
|
|
51
|
+
method_handler: grpc.RpcMethodHandler = continuation(handler_call_details)
|
|
52
|
+
return self._generic_license_unary_method_handler(method_handler)
|
|
53
|
+
|
|
54
|
+
def _generic_license_unary_method_handler(
|
|
55
|
+
self, method_handler: grpc.RpcMethodHandler
|
|
56
|
+
) -> grpc.RpcMethodHandler:
|
|
57
|
+
def _generic_method_handler(
|
|
58
|
+
request: GrpcMessage,
|
|
59
|
+
context: grpc.ServicerContext,
|
|
60
|
+
) -> Union[GrpcMessage, Iterator[GrpcMessage]]:
|
|
61
|
+
"""Handle the method call with license checking."""
|
|
62
|
+
call = method_handler.unary_unary or method_handler.unary_stream
|
|
63
|
+
|
|
64
|
+
if not self.license_plugin.check_license():
|
|
65
|
+
context.abort(
|
|
66
|
+
grpc.StatusCode.PERMISSION_DENIED,
|
|
67
|
+
"❗️ License check failed. Please contact the SuperLink "
|
|
68
|
+
"administrator.",
|
|
69
|
+
)
|
|
70
|
+
raise grpc.RpcError()
|
|
71
|
+
|
|
72
|
+
return call(request, context) # type: ignore
|
|
73
|
+
|
|
74
|
+
if method_handler.unary_unary:
|
|
75
|
+
message_handler = grpc.unary_unary_rpc_method_handler
|
|
76
|
+
else:
|
|
77
|
+
message_handler = grpc.unary_stream_rpc_method_handler
|
|
78
|
+
return message_handler(
|
|
79
|
+
_generic_method_handler,
|
|
80
|
+
request_deserializer=method_handler.request_deserializer,
|
|
81
|
+
response_serializer=method_handler.response_serializer,
|
|
82
|
+
)
|