flwr 1.20.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/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 +11 -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 +1 -1
- flwr/cli/new/templates/app/pyproject.flowertune.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.huggingface.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.jax.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.mlx.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.numpy.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.pytorch_msg_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/run/run.py +9 -13
- flwr/cli/stop.py +7 -4
- flwr/cli/utils.py +19 -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 +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 +23 -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 +26 -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/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 +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/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 +129 -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/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 +161 -164
- flwr/supercore/app_utils.py +58 -0
- flwr/{supernode/scheduler → supercore/cli}/__init__.py +3 -3
- flwr/supercore/cli/flower_superexec.py +141 -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} +4 -4
- 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/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} +24 -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} +69 -30
- 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.21.0.dist-info}/METADATA +4 -3
- {flwr-1.20.0.dist-info → flwr-1.21.0.dist-info}/RECORD +127 -98
- {flwr-1.20.0.dist-info → flwr-1.21.0.dist-info}/entry_points.txt +1 -0
- flwr/proto/exec_pb2.py +0 -62
- 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/proto/{exec_pb2.pyi → control_pb2.pyi} +0 -0
- {flwr-1.20.0.dist-info → flwr-1.21.0.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,81 @@
|
|
|
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 CoreState."""
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
from abc import ABC, abstractmethod
|
|
19
|
+
from typing import Optional
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class CoreState(ABC):
|
|
23
|
+
"""Abstract base class for core state."""
|
|
24
|
+
|
|
25
|
+
@abstractmethod
|
|
26
|
+
def create_token(self, run_id: int) -> Optional[str]:
|
|
27
|
+
"""Create a token for the given run ID.
|
|
28
|
+
|
|
29
|
+
Parameters
|
|
30
|
+
----------
|
|
31
|
+
run_id : int
|
|
32
|
+
The ID of the run for which to create a token.
|
|
33
|
+
|
|
34
|
+
Returns
|
|
35
|
+
-------
|
|
36
|
+
str
|
|
37
|
+
The newly generated token if one does not already exist
|
|
38
|
+
for the given run ID, otherwise None.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
@abstractmethod
|
|
42
|
+
def verify_token(self, run_id: int, token: str) -> bool:
|
|
43
|
+
"""Verify a token for the given run ID.
|
|
44
|
+
|
|
45
|
+
Parameters
|
|
46
|
+
----------
|
|
47
|
+
run_id : int
|
|
48
|
+
The ID of the run for which to verify the token.
|
|
49
|
+
token : str
|
|
50
|
+
The token to verify.
|
|
51
|
+
|
|
52
|
+
Returns
|
|
53
|
+
-------
|
|
54
|
+
bool
|
|
55
|
+
True if the token is valid for the run ID, False otherwise.
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
@abstractmethod
|
|
59
|
+
def delete_token(self, run_id: int) -> None:
|
|
60
|
+
"""Delete the token for the given run ID.
|
|
61
|
+
|
|
62
|
+
Parameters
|
|
63
|
+
----------
|
|
64
|
+
run_id : int
|
|
65
|
+
The ID of the run for which to delete the token.
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
@abstractmethod
|
|
69
|
+
def get_run_id_by_token(self, token: str) -> Optional[int]:
|
|
70
|
+
"""Get the run ID associated with a given token.
|
|
71
|
+
|
|
72
|
+
Parameters
|
|
73
|
+
----------
|
|
74
|
+
token : str
|
|
75
|
+
The token to look up.
|
|
76
|
+
|
|
77
|
+
Returns
|
|
78
|
+
-------
|
|
79
|
+
Optional[int]
|
|
80
|
+
The run ID if the token is valid, otherwise None.
|
|
81
|
+
"""
|
|
@@ -15,8 +15,11 @@
|
|
|
15
15
|
"""GRPC health servicers."""
|
|
16
16
|
|
|
17
17
|
|
|
18
|
+
from .health_server import add_args_health, run_health_server_grpc_no_tls
|
|
18
19
|
from .simple_health_servicer import SimpleHealthServicer
|
|
19
20
|
|
|
20
21
|
__all__ = [
|
|
21
22
|
"SimpleHealthServicer",
|
|
23
|
+
"add_args_health",
|
|
24
|
+
"run_health_server_grpc_no_tls",
|
|
22
25
|
]
|
|
@@ -0,0 +1,53 @@
|
|
|
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
|
+
"""Health servers."""
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
import argparse
|
|
19
|
+
from logging import INFO
|
|
20
|
+
|
|
21
|
+
import grpc
|
|
22
|
+
from grpc_health.v1.health_pb2_grpc import add_HealthServicer_to_server
|
|
23
|
+
|
|
24
|
+
from flwr.common.grpc import generic_create_grpc_server
|
|
25
|
+
from flwr.common.logger import log
|
|
26
|
+
|
|
27
|
+
from .simple_health_servicer import SimpleHealthServicer
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def run_health_server_grpc_no_tls(address: str) -> grpc.Server:
|
|
31
|
+
"""Run gRPC health server with no TLS."""
|
|
32
|
+
health_server = generic_create_grpc_server(
|
|
33
|
+
servicer_and_add_fn=(
|
|
34
|
+
SimpleHealthServicer(),
|
|
35
|
+
add_HealthServicer_to_server,
|
|
36
|
+
),
|
|
37
|
+
server_address=address,
|
|
38
|
+
certificates=None,
|
|
39
|
+
)
|
|
40
|
+
log(INFO, "Starting gRPC health server on %s", address)
|
|
41
|
+
health_server.start()
|
|
42
|
+
return health_server
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def add_args_health(parser: argparse.ArgumentParser) -> None:
|
|
46
|
+
"""Add arguments for health server."""
|
|
47
|
+
parser.add_argument(
|
|
48
|
+
"--health-server-address",
|
|
49
|
+
type=str,
|
|
50
|
+
default=None,
|
|
51
|
+
help="Health service gRPC server address (IPv4, IPv6, or a domain name) "
|
|
52
|
+
"with no TLS. If not set, the health server will not be started.",
|
|
53
|
+
)
|
|
@@ -28,11 +28,11 @@ class SimpleHealthServicer(HealthServicer): # type: ignore
|
|
|
28
28
|
"""A simple gRPC health servicer that always returns SERVING."""
|
|
29
29
|
|
|
30
30
|
def Check(
|
|
31
|
-
self, request: HealthCheckRequest, context: grpc.
|
|
31
|
+
self, request: HealthCheckRequest, context: grpc.ServicerContext
|
|
32
32
|
) -> HealthCheckResponse:
|
|
33
33
|
"""Return a HealthCheckResponse with SERVING status."""
|
|
34
34
|
return HealthCheckResponse(status=HealthCheckResponse.SERVING)
|
|
35
35
|
|
|
36
|
-
def Watch(self, request: HealthCheckRequest, context: grpc.
|
|
36
|
+
def Watch(self, request: HealthCheckRequest, context: grpc.ServicerContext) -> None:
|
|
37
37
|
"""Watch the health status (not implemented)."""
|
|
38
38
|
context.abort(grpc.StatusCode.UNIMPLEMENTED, "Watch is not implemented")
|
|
@@ -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
|
+
"""Flower SuperExec plugins."""
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
from .clientapp_exec_plugin import ClientAppExecPlugin
|
|
19
|
+
from .exec_plugin import ExecPlugin
|
|
20
|
+
from .serverapp_exec_plugin import ServerAppExecPlugin
|
|
21
|
+
from .simulation_exec_plugin import SimulationExecPlugin
|
|
22
|
+
|
|
23
|
+
__all__ = [
|
|
24
|
+
"ClientAppExecPlugin",
|
|
25
|
+
"ExecPlugin",
|
|
26
|
+
"ServerAppExecPlugin",
|
|
27
|
+
"SimulationExecPlugin",
|
|
28
|
+
]
|
|
@@ -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
|
-
"""Simple Flower
|
|
15
|
+
"""Simple base Flower SuperExec plugin for app processes."""
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
import os
|
|
@@ -20,15 +20,19 @@ import subprocess
|
|
|
20
20
|
from collections.abc import Sequence
|
|
21
21
|
from typing import Optional
|
|
22
22
|
|
|
23
|
-
from
|
|
23
|
+
from .exec_plugin import ExecPlugin
|
|
24
24
|
|
|
25
25
|
|
|
26
|
-
class
|
|
27
|
-
"""Simple Flower
|
|
26
|
+
class BaseExecPlugin(ExecPlugin):
|
|
27
|
+
"""Simple Flower SuperExec plugin for app processes.
|
|
28
28
|
|
|
29
29
|
The plugin always selects the first candidate run ID.
|
|
30
30
|
"""
|
|
31
31
|
|
|
32
|
+
# Placeholders to be defined in subclasses
|
|
33
|
+
command = ""
|
|
34
|
+
appio_api_address_arg = ""
|
|
35
|
+
|
|
32
36
|
def select_run_id(self, candidate_run_ids: Sequence[int]) -> Optional[int]:
|
|
33
37
|
"""Select a run ID to execute from a sequence of candidates."""
|
|
34
38
|
if not candidate_run_ids:
|
|
@@ -37,8 +41,8 @@ class SimpleClientAppSchedulerPlugin(SchedulerPlugin):
|
|
|
37
41
|
|
|
38
42
|
def launch_app(self, token: str, run_id: int) -> None:
|
|
39
43
|
"""Launch the application associated with a given run ID and token."""
|
|
40
|
-
cmds = [
|
|
41
|
-
cmds += [
|
|
44
|
+
cmds = [self.command, "--insecure"]
|
|
45
|
+
cmds += [self.appio_api_address_arg, self.appio_api_address]
|
|
42
46
|
cmds += ["--token", token]
|
|
43
47
|
cmds += ["--parent-pid", str(os.getpid())]
|
|
44
48
|
cmds += ["--flwr-dir", self.flwr_dir]
|
|
@@ -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 ClientApp."""
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
from .base_exec_plugin import BaseExecPlugin
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class ClientAppExecPlugin(BaseExecPlugin):
|
|
22
|
+
"""Simple Flower SuperExec plugin for ClientApp.
|
|
23
|
+
|
|
24
|
+
The plugin always selects the first candidate run ID.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
command = "flwr-clientapp"
|
|
28
|
+
appio_api_address_arg = "--clientappio-api-address"
|
|
@@ -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
|
-
"""Abstract base class
|
|
15
|
+
"""Abstract base class ExecPlugin."""
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
from abc import ABC, abstractmethod
|
|
@@ -22,8 +22,8 @@ from typing import Callable, Optional
|
|
|
22
22
|
from flwr.common.typing import Run
|
|
23
23
|
|
|
24
24
|
|
|
25
|
-
class
|
|
26
|
-
"""Abstract base class for
|
|
25
|
+
class ExecPlugin(ABC):
|
|
26
|
+
"""Abstract base class for SuperExec plugins."""
|
|
27
27
|
|
|
28
28
|
def __init__(
|
|
29
29
|
self,
|
|
@@ -59,7 +59,7 @@ class SchedulerPlugin(ABC):
|
|
|
59
59
|
|
|
60
60
|
This method starts the application process using the given `token`.
|
|
61
61
|
The `run_id` is used solely for bookkeeping purposes, allowing any
|
|
62
|
-
|
|
62
|
+
plugin implementation to associate this launch with a specific run.
|
|
63
63
|
|
|
64
64
|
Parameters
|
|
65
65
|
----------
|
|
@@ -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
|
+
)
|
|
@@ -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)
|