flwr 1.14.0__py3-none-any.whl → 1.15.1__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/cli/auth_plugin/__init__.py +31 -0
- flwr/cli/auth_plugin/oidc_cli_plugin.py +150 -0
- flwr/cli/cli_user_auth_interceptor.py +6 -2
- flwr/cli/config_utils.py +24 -147
- flwr/cli/constant.py +27 -0
- flwr/cli/install.py +1 -1
- flwr/cli/log.py +18 -3
- flwr/cli/login/login.py +43 -8
- flwr/cli/ls.py +14 -5
- flwr/cli/new/templates/app/README.md.tpl +3 -2
- 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 +2 -2
- flwr/cli/new/templates/app/pyproject.mlx.toml.tpl +2 -2
- flwr/cli/new/templates/app/pyproject.numpy.toml.tpl +2 -2
- flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl +4 -4
- flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl +3 -3
- flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl +2 -2
- flwr/cli/run/run.py +21 -11
- flwr/cli/stop.py +13 -4
- flwr/cli/utils.py +54 -40
- flwr/client/app.py +36 -48
- flwr/client/clientapp/app.py +19 -25
- flwr/client/clientapp/utils.py +1 -1
- flwr/client/grpc_client/connection.py +1 -12
- flwr/client/grpc_rere_client/client_interceptor.py +19 -119
- flwr/client/grpc_rere_client/connection.py +46 -36
- flwr/client/grpc_rere_client/grpc_adapter.py +12 -12
- flwr/client/message_handler/task_handler.py +0 -17
- flwr/client/rest_client/connection.py +34 -26
- flwr/client/supernode/app.py +18 -72
- flwr/common/args.py +25 -47
- flwr/common/auth_plugin/auth_plugin.py +34 -23
- flwr/common/config.py +166 -16
- flwr/common/constant.py +24 -9
- flwr/common/differential_privacy.py +2 -1
- flwr/common/exit/__init__.py +24 -0
- flwr/common/exit/exit.py +99 -0
- flwr/common/exit/exit_code.py +93 -0
- flwr/common/exit_handlers.py +32 -30
- flwr/common/grpc.py +167 -4
- flwr/common/logger.py +26 -7
- flwr/common/object_ref.py +0 -14
- flwr/common/record/recordset.py +1 -1
- flwr/common/secure_aggregation/crypto/symmetric_encryption.py +45 -0
- flwr/common/serde.py +6 -4
- flwr/common/typing.py +20 -0
- flwr/proto/clientappio_pb2.py +1 -1
- flwr/proto/error_pb2.py +1 -1
- flwr/proto/exec_pb2.py +13 -25
- flwr/proto/exec_pb2.pyi +27 -54
- flwr/proto/fab_pb2.py +1 -1
- flwr/proto/fleet_pb2.py +31 -31
- flwr/proto/fleet_pb2.pyi +23 -23
- flwr/proto/fleet_pb2_grpc.py +30 -30
- flwr/proto/fleet_pb2_grpc.pyi +20 -20
- flwr/proto/grpcadapter_pb2.py +1 -1
- flwr/proto/log_pb2.py +1 -1
- flwr/proto/message_pb2.py +1 -1
- flwr/proto/node_pb2.py +3 -3
- flwr/proto/node_pb2.pyi +1 -4
- flwr/proto/recordset_pb2.py +1 -1
- flwr/proto/run_pb2.py +1 -1
- flwr/proto/serverappio_pb2.py +24 -25
- flwr/proto/serverappio_pb2.pyi +26 -32
- flwr/proto/serverappio_pb2_grpc.py +28 -28
- flwr/proto/serverappio_pb2_grpc.pyi +16 -16
- flwr/proto/simulationio_pb2.py +1 -1
- flwr/proto/task_pb2.py +1 -1
- flwr/proto/transport_pb2.py +1 -1
- flwr/server/app.py +116 -128
- flwr/server/compat/app_utils.py +0 -1
- flwr/server/compat/driver_client_proxy.py +1 -2
- flwr/server/driver/grpc_driver.py +32 -27
- flwr/server/driver/inmemory_driver.py +2 -1
- flwr/server/serverapp/app.py +12 -10
- flwr/server/superlink/driver/serverappio_grpc.py +1 -1
- flwr/server/superlink/driver/serverappio_servicer.py +74 -48
- flwr/server/superlink/fleet/grpc_adapter/grpc_adapter_servicer.py +20 -88
- flwr/server/superlink/fleet/grpc_bidi/grpc_server.py +2 -165
- flwr/server/superlink/fleet/grpc_rere/fleet_servicer.py +25 -24
- flwr/server/superlink/fleet/grpc_rere/server_interceptor.py +110 -168
- flwr/server/superlink/fleet/message_handler/message_handler.py +37 -24
- flwr/server/superlink/fleet/rest_rere/rest_api.py +16 -18
- flwr/server/superlink/fleet/vce/vce_api.py +2 -2
- flwr/server/superlink/linkstate/in_memory_linkstate.py +45 -75
- flwr/server/superlink/linkstate/linkstate.py +17 -38
- flwr/server/superlink/linkstate/sqlite_linkstate.py +81 -145
- flwr/server/superlink/linkstate/utils.py +18 -8
- flwr/server/superlink/simulation/simulationio_grpc.py +1 -1
- flwr/server/utils/validator.py +9 -34
- flwr/simulation/app.py +4 -6
- flwr/simulation/legacy_app.py +4 -2
- flwr/simulation/run_simulation.py +1 -1
- flwr/simulation/simulationio_connection.py +2 -1
- flwr/superexec/exec_grpc.py +1 -1
- flwr/superexec/exec_servicer.py +23 -2
- {flwr-1.14.0.dist-info → flwr-1.15.1.dist-info}/METADATA +8 -8
- {flwr-1.14.0.dist-info → flwr-1.15.1.dist-info}/RECORD +103 -97
- {flwr-1.14.0.dist-info → flwr-1.15.1.dist-info}/LICENSE +0 -0
- {flwr-1.14.0.dist-info → flwr-1.15.1.dist-info}/WHEEL +0 -0
- {flwr-1.14.0.dist-info → flwr-1.15.1.dist-info}/entry_points.txt +0 -0
|
@@ -18,8 +18,9 @@ Refer to the [How to Run Simulations](https://flower.ai/docs/framework/how-to-ru
|
|
|
18
18
|
|
|
19
19
|
## Run with the Deployment Engine
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
Follow this [how-to guide](https://flower.ai/docs/framework/how-to-run-flower-with-deployment-engine.html) to run the same app in this example but with Flower's Deployment Engine. After that, you might be intersted in setting up [secure TLS-enabled communications](https://flower.ai/docs/framework/how-to-enable-tls-connections.html) and [SuperNode authentication](https://flower.ai/docs/framework/how-to-authenticate-supernodes.html) in your federation.
|
|
22
|
+
|
|
23
|
+
You can run Flower on Docker too! Check out the [Flower with Docker](https://flower.ai/docs/framework/docker/index.html) documentation.
|
|
23
24
|
|
|
24
25
|
## Resources
|
|
25
26
|
|
|
@@ -8,10 +8,10 @@ version = "1.0.0"
|
|
|
8
8
|
description = ""
|
|
9
9
|
license = "Apache-2.0"
|
|
10
10
|
dependencies = [
|
|
11
|
-
"flwr[simulation]>=1.
|
|
12
|
-
"flwr-datasets[vision]>=0.
|
|
13
|
-
"torch==2.
|
|
14
|
-
"torchvision==0.
|
|
11
|
+
"flwr[simulation]>=1.15.1",
|
|
12
|
+
"flwr-datasets[vision]>=0.5.0",
|
|
13
|
+
"torch==2.5.1",
|
|
14
|
+
"torchvision==0.20.1",
|
|
15
15
|
]
|
|
16
16
|
|
|
17
17
|
[tool.hatch.metadata]
|
|
@@ -8,8 +8,8 @@ version = "1.0.0"
|
|
|
8
8
|
description = ""
|
|
9
9
|
license = "Apache-2.0"
|
|
10
10
|
dependencies = [
|
|
11
|
-
"flwr[simulation]>=1.
|
|
12
|
-
"flwr-datasets>=0.
|
|
11
|
+
"flwr[simulation]>=1.15.1",
|
|
12
|
+
"flwr-datasets>=0.5.0",
|
|
13
13
|
"torch==2.3.1",
|
|
14
14
|
"trl==0.8.1",
|
|
15
15
|
"bitsandbytes==0.45.0",
|
|
@@ -8,13 +8,13 @@ version = "1.0.0"
|
|
|
8
8
|
description = ""
|
|
9
9
|
license = "Apache-2.0"
|
|
10
10
|
dependencies = [
|
|
11
|
-
"flwr[simulation]>=1.
|
|
12
|
-
"flwr-datasets>=0.
|
|
13
|
-
"torch==2.
|
|
11
|
+
"flwr[simulation]>=1.15.1",
|
|
12
|
+
"flwr-datasets>=0.5.0",
|
|
13
|
+
"torch==2.5.1",
|
|
14
14
|
"transformers>=4.30.0,<5.0",
|
|
15
15
|
"evaluate>=0.4.0,<1.0",
|
|
16
16
|
"datasets>=2.0.0, <3.0",
|
|
17
|
-
"scikit-learn>=1.
|
|
17
|
+
"scikit-learn>=1.6.1, <2.0",
|
|
18
18
|
]
|
|
19
19
|
|
|
20
20
|
[tool.hatch.build.targets.wheel]
|
|
@@ -8,10 +8,10 @@ version = "1.0.0"
|
|
|
8
8
|
description = ""
|
|
9
9
|
license = "Apache-2.0"
|
|
10
10
|
dependencies = [
|
|
11
|
-
"flwr[simulation]>=1.
|
|
11
|
+
"flwr[simulation]>=1.15.1",
|
|
12
12
|
"jax==0.4.30",
|
|
13
13
|
"jaxlib==0.4.30",
|
|
14
|
-
"scikit-learn==1.
|
|
14
|
+
"scikit-learn==1.6.1",
|
|
15
15
|
]
|
|
16
16
|
|
|
17
17
|
[tool.hatch.build.targets.wheel]
|
|
@@ -8,10 +8,10 @@ version = "1.0.0"
|
|
|
8
8
|
description = ""
|
|
9
9
|
license = "Apache-2.0"
|
|
10
10
|
dependencies = [
|
|
11
|
-
"flwr[simulation]>=1.
|
|
12
|
-
"flwr-datasets[vision]>=0.
|
|
13
|
-
"torch==2.
|
|
14
|
-
"torchvision==0.
|
|
11
|
+
"flwr[simulation]>=1.15.1",
|
|
12
|
+
"flwr-datasets[vision]>=0.5.0",
|
|
13
|
+
"torch==2.5.1",
|
|
14
|
+
"torchvision==0.20.1",
|
|
15
15
|
]
|
|
16
16
|
|
|
17
17
|
[tool.hatch.build.targets.wheel]
|
|
@@ -8,9 +8,9 @@ version = "1.0.0"
|
|
|
8
8
|
description = ""
|
|
9
9
|
license = "Apache-2.0"
|
|
10
10
|
dependencies = [
|
|
11
|
-
"flwr[simulation]>=1.
|
|
12
|
-
"flwr-datasets[vision]>=0.
|
|
13
|
-
"scikit-learn>=1.
|
|
11
|
+
"flwr[simulation]>=1.15.1",
|
|
12
|
+
"flwr-datasets[vision]>=0.5.0",
|
|
13
|
+
"scikit-learn>=1.6.1",
|
|
14
14
|
]
|
|
15
15
|
|
|
16
16
|
[tool.hatch.build.targets.wheel]
|
flwr/cli/run/run.py
CHANGED
|
@@ -31,6 +31,7 @@ from flwr.cli.config_utils import (
|
|
|
31
31
|
process_loaded_project_config,
|
|
32
32
|
validate_federation_in_project_config,
|
|
33
33
|
)
|
|
34
|
+
from flwr.cli.constant import FEDERATION_CONFIG_HELP_MESSAGE
|
|
34
35
|
from flwr.common.config import (
|
|
35
36
|
flatten_dict,
|
|
36
37
|
parse_config_args,
|
|
@@ -57,7 +58,7 @@ from ..utils import (
|
|
|
57
58
|
CONN_REFRESH_PERIOD = 60 # Connection refresh period for log streaming (seconds)
|
|
58
59
|
|
|
59
60
|
|
|
60
|
-
# pylint: disable-next=too-many-locals
|
|
61
|
+
# pylint: disable-next=too-many-locals, R0913, R0917
|
|
61
62
|
def run(
|
|
62
63
|
app: Annotated[
|
|
63
64
|
Path,
|
|
@@ -67,16 +68,23 @@ def run(
|
|
|
67
68
|
Optional[str],
|
|
68
69
|
typer.Argument(help="Name of the federation to run the app on."),
|
|
69
70
|
] = None,
|
|
70
|
-
|
|
71
|
+
run_config_overrides: Annotated[
|
|
71
72
|
Optional[list[str]],
|
|
72
73
|
typer.Option(
|
|
73
74
|
"--run-config",
|
|
74
75
|
"-c",
|
|
75
|
-
help="Override configuration
|
|
76
|
-
|
|
77
|
-
"
|
|
78
|
-
"
|
|
79
|
-
"
|
|
76
|
+
help="Override run configuration values in the format:\n\n"
|
|
77
|
+
"`--run-config 'key1=value1 key2=value2' --run-config 'key3=value3'`\n\n"
|
|
78
|
+
"Values can be of any type supported in TOML, such as bool, int, "
|
|
79
|
+
"float, or string. Ensure that the keys (`key1`, `key2`, `key3` "
|
|
80
|
+
"in this example) exist in `pyproject.toml` for proper overriding.",
|
|
81
|
+
),
|
|
82
|
+
] = None,
|
|
83
|
+
federation_config_overrides: Annotated[
|
|
84
|
+
Optional[list[str]],
|
|
85
|
+
typer.Option(
|
|
86
|
+
"--federation-config",
|
|
87
|
+
help=FEDERATION_CONFIG_HELP_MESSAGE,
|
|
80
88
|
),
|
|
81
89
|
] = None,
|
|
82
90
|
stream: Annotated[
|
|
@@ -108,7 +116,7 @@ def run(
|
|
|
108
116
|
config, errors, warnings = load_and_validate(path=pyproject_path)
|
|
109
117
|
config = process_loaded_project_config(config, errors, warnings)
|
|
110
118
|
federation, federation_config = validate_federation_in_project_config(
|
|
111
|
-
federation, config
|
|
119
|
+
federation, config, federation_config_overrides
|
|
112
120
|
)
|
|
113
121
|
|
|
114
122
|
if "address" in federation_config:
|
|
@@ -116,12 +124,14 @@ def run(
|
|
|
116
124
|
app,
|
|
117
125
|
federation,
|
|
118
126
|
federation_config,
|
|
119
|
-
|
|
127
|
+
run_config_overrides,
|
|
120
128
|
stream,
|
|
121
129
|
output_format,
|
|
122
130
|
)
|
|
123
131
|
else:
|
|
124
|
-
_run_without_exec_api(
|
|
132
|
+
_run_without_exec_api(
|
|
133
|
+
app, federation_config, run_config_overrides, federation
|
|
134
|
+
)
|
|
125
135
|
except (typer.Exit, Exception) as err: # pylint: disable=broad-except
|
|
126
136
|
if suppress_output:
|
|
127
137
|
restore_output()
|
|
@@ -148,7 +158,7 @@ def _run_with_exec_api(
|
|
|
148
158
|
stream: bool,
|
|
149
159
|
output_format: str,
|
|
150
160
|
) -> None:
|
|
151
|
-
auth_plugin = try_obtain_cli_auth_plugin(app, federation)
|
|
161
|
+
auth_plugin = try_obtain_cli_auth_plugin(app, federation, federation_config)
|
|
152
162
|
channel = init_channel(app, federation_config, auth_plugin)
|
|
153
163
|
stub = ExecStub(channel)
|
|
154
164
|
|
flwr/cli/stop.py
CHANGED
|
@@ -29,6 +29,7 @@ from flwr.cli.config_utils import (
|
|
|
29
29
|
process_loaded_project_config,
|
|
30
30
|
validate_federation_in_project_config,
|
|
31
31
|
)
|
|
32
|
+
from flwr.cli.constant import FEDERATION_CONFIG_HELP_MESSAGE
|
|
32
33
|
from flwr.common.constant import FAB_CONFIG_FILE, CliOutputFormat
|
|
33
34
|
from flwr.common.logger import print_json_error, redirect_output, restore_output
|
|
34
35
|
from flwr.proto.exec_pb2 import StopRunRequest, StopRunResponse # pylint: disable=E0611
|
|
@@ -50,6 +51,13 @@ def stop( # pylint: disable=R0914
|
|
|
50
51
|
Optional[str],
|
|
51
52
|
typer.Argument(help="Name of the federation"),
|
|
52
53
|
] = None,
|
|
54
|
+
federation_config_overrides: Annotated[
|
|
55
|
+
Optional[list[str]],
|
|
56
|
+
typer.Option(
|
|
57
|
+
"--federation-config",
|
|
58
|
+
help=FEDERATION_CONFIG_HELP_MESSAGE,
|
|
59
|
+
),
|
|
60
|
+
] = None,
|
|
53
61
|
output_format: Annotated[
|
|
54
62
|
str,
|
|
55
63
|
typer.Option(
|
|
@@ -73,12 +81,12 @@ def stop( # pylint: disable=R0914
|
|
|
73
81
|
config, errors, warnings = load_and_validate(path=pyproject_path)
|
|
74
82
|
config = process_loaded_project_config(config, errors, warnings)
|
|
75
83
|
federation, federation_config = validate_federation_in_project_config(
|
|
76
|
-
federation, config
|
|
84
|
+
federation, config, federation_config_overrides
|
|
77
85
|
)
|
|
78
86
|
exit_if_no_address(federation_config, "stop")
|
|
79
|
-
|
|
87
|
+
channel = None
|
|
80
88
|
try:
|
|
81
|
-
auth_plugin = try_obtain_cli_auth_plugin(app, federation)
|
|
89
|
+
auth_plugin = try_obtain_cli_auth_plugin(app, federation, federation_config)
|
|
82
90
|
channel = init_channel(app, federation_config, auth_plugin)
|
|
83
91
|
stub = ExecStub(channel) # pylint: disable=unused-variable # noqa: F841
|
|
84
92
|
|
|
@@ -93,7 +101,8 @@ def stop( # pylint: disable=R0914
|
|
|
93
101
|
)
|
|
94
102
|
raise typer.Exit(code=1) from err
|
|
95
103
|
finally:
|
|
96
|
-
channel
|
|
104
|
+
if channel:
|
|
105
|
+
channel.close()
|
|
97
106
|
except (typer.Exit, Exception) as err: # pylint: disable=broad-except
|
|
98
107
|
if suppress_output:
|
|
99
108
|
restore_output()
|
flwr/cli/utils.py
CHANGED
|
@@ -20,7 +20,6 @@ import json
|
|
|
20
20
|
import re
|
|
21
21
|
from collections.abc import Iterator
|
|
22
22
|
from contextlib import contextmanager
|
|
23
|
-
from logging import DEBUG
|
|
24
23
|
from pathlib import Path
|
|
25
24
|
from typing import Any, Callable, Optional, Union, cast
|
|
26
25
|
|
|
@@ -29,20 +28,16 @@ import typer
|
|
|
29
28
|
|
|
30
29
|
from flwr.cli.cli_user_auth_interceptor import CliUserAuthInterceptor
|
|
31
30
|
from flwr.common.auth_plugin import CliAuthPlugin
|
|
32
|
-
from flwr.common.constant import
|
|
33
|
-
from flwr.common.grpc import
|
|
34
|
-
|
|
35
|
-
|
|
31
|
+
from flwr.common.constant import AUTH_TYPE_JSON_KEY, CREDENTIALS_DIR, FLWR_DIR
|
|
32
|
+
from flwr.common.grpc import (
|
|
33
|
+
GRPC_MAX_MESSAGE_LENGTH,
|
|
34
|
+
create_channel,
|
|
35
|
+
on_channel_state_change,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
from .auth_plugin import get_cli_auth_plugins
|
|
36
39
|
from .config_utils import validate_certificate_in_federation_config
|
|
37
40
|
|
|
38
|
-
try:
|
|
39
|
-
from flwr.ee import get_cli_auth_plugins
|
|
40
|
-
except ImportError:
|
|
41
|
-
|
|
42
|
-
def get_cli_auth_plugins() -> dict[str, type[CliAuthPlugin]]:
|
|
43
|
-
"""Return all CLI authentication plugins."""
|
|
44
|
-
raise NotImplementedError("No authentication plugins are currently supported.")
|
|
45
|
-
|
|
46
41
|
|
|
47
42
|
def prompt_text(
|
|
48
43
|
text: str,
|
|
@@ -217,25 +212,42 @@ def get_user_auth_config_path(root_dir: Path, federation: str) -> Path:
|
|
|
217
212
|
def try_obtain_cli_auth_plugin(
|
|
218
213
|
root_dir: Path,
|
|
219
214
|
federation: str,
|
|
215
|
+
federation_config: dict[str, Any],
|
|
220
216
|
auth_type: Optional[str] = None,
|
|
221
217
|
) -> Optional[CliAuthPlugin]:
|
|
222
218
|
"""Load the CLI-side user auth plugin for the given auth type."""
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
# Load the config file if it exists
|
|
226
|
-
config: dict[str, Any] = {}
|
|
227
|
-
if config_path.exists():
|
|
228
|
-
with config_path.open("r", encoding="utf-8") as file:
|
|
229
|
-
config = json.load(file)
|
|
230
|
-
# This is the case when the user auth is not enabled
|
|
231
|
-
elif auth_type is None:
|
|
219
|
+
# Check if user auth is enabled
|
|
220
|
+
if not federation_config.get("enable-user-auth", False):
|
|
232
221
|
return None
|
|
233
222
|
|
|
223
|
+
# Check if TLS is enabled. If not, raise an error
|
|
224
|
+
if federation_config.get("root-certificates") is None:
|
|
225
|
+
typer.secho(
|
|
226
|
+
"❌ User authentication requires TLS to be enabled. "
|
|
227
|
+
"Please provide 'root-certificates' in the federation"
|
|
228
|
+
" configuration.",
|
|
229
|
+
fg=typer.colors.RED,
|
|
230
|
+
bold=True,
|
|
231
|
+
)
|
|
232
|
+
raise typer.Exit(code=1)
|
|
233
|
+
|
|
234
|
+
config_path = get_user_auth_config_path(root_dir, federation)
|
|
235
|
+
|
|
234
236
|
# Get the auth type from the config if not provided
|
|
237
|
+
# auth_type will be None for all CLI commands except login
|
|
235
238
|
if auth_type is None:
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
+
try:
|
|
240
|
+
with config_path.open("r", encoding="utf-8") as file:
|
|
241
|
+
json_file = json.load(file)
|
|
242
|
+
auth_type = json_file[AUTH_TYPE_JSON_KEY]
|
|
243
|
+
except (FileNotFoundError, KeyError):
|
|
244
|
+
typer.secho(
|
|
245
|
+
"❌ Missing or invalid credentials for user authentication. "
|
|
246
|
+
"Please run `flwr login` to authenticate.",
|
|
247
|
+
fg=typer.colors.RED,
|
|
248
|
+
bold=True,
|
|
249
|
+
)
|
|
250
|
+
raise typer.Exit(code=1) from None
|
|
239
251
|
|
|
240
252
|
# Retrieve auth plugin class and instantiate it
|
|
241
253
|
try:
|
|
@@ -254,11 +266,6 @@ def init_channel(
|
|
|
254
266
|
app: Path, federation_config: dict[str, Any], auth_plugin: Optional[CliAuthPlugin]
|
|
255
267
|
) -> grpc.Channel:
|
|
256
268
|
"""Initialize gRPC channel to the Exec API."""
|
|
257
|
-
|
|
258
|
-
def on_channel_state_change(channel_connectivity: str) -> None:
|
|
259
|
-
"""Log channel connectivity."""
|
|
260
|
-
log(DEBUG, channel_connectivity)
|
|
261
|
-
|
|
262
269
|
insecure, root_certificates_bytes = validate_certificate_in_federation_config(
|
|
263
270
|
app, federation_config
|
|
264
271
|
)
|
|
@@ -267,7 +274,7 @@ def init_channel(
|
|
|
267
274
|
interceptors: list[grpc.UnaryUnaryClientInterceptor] = []
|
|
268
275
|
if auth_plugin is not None:
|
|
269
276
|
auth_plugin.load_tokens()
|
|
270
|
-
interceptors
|
|
277
|
+
interceptors.append(CliUserAuthInterceptor(auth_plugin))
|
|
271
278
|
|
|
272
279
|
# Create the gRPC channel
|
|
273
280
|
channel = create_channel(
|
|
@@ -291,12 +298,19 @@ def unauthenticated_exc_handler() -> Iterator[None]:
|
|
|
291
298
|
try:
|
|
292
299
|
yield
|
|
293
300
|
except grpc.RpcError as e:
|
|
294
|
-
if e.code()
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
301
|
+
if e.code() == grpc.StatusCode.UNAUTHENTICATED:
|
|
302
|
+
typer.secho(
|
|
303
|
+
"❌ Authentication failed. Please run `flwr login`"
|
|
304
|
+
" to authenticate and try again.",
|
|
305
|
+
fg=typer.colors.RED,
|
|
306
|
+
bold=True,
|
|
307
|
+
)
|
|
308
|
+
raise typer.Exit(code=1) from None
|
|
309
|
+
if e.code() == grpc.StatusCode.UNIMPLEMENTED:
|
|
310
|
+
typer.secho(
|
|
311
|
+
"❌ User authentication is not enabled on this SuperLink.",
|
|
312
|
+
fg=typer.colors.RED,
|
|
313
|
+
bold=True,
|
|
314
|
+
)
|
|
315
|
+
raise typer.Exit(code=1) from None
|
|
316
|
+
raise
|
flwr/client/app.py
CHANGED
|
@@ -15,13 +15,14 @@
|
|
|
15
15
|
"""Flower client app."""
|
|
16
16
|
|
|
17
17
|
|
|
18
|
-
import
|
|
19
|
-
import
|
|
18
|
+
import multiprocessing
|
|
19
|
+
import os
|
|
20
20
|
import sys
|
|
21
|
+
import threading
|
|
21
22
|
import time
|
|
22
23
|
from contextlib import AbstractContextManager
|
|
23
|
-
from dataclasses import dataclass
|
|
24
24
|
from logging import ERROR, INFO, WARN
|
|
25
|
+
from os import urandom
|
|
25
26
|
from pathlib import Path
|
|
26
27
|
from typing import Callable, Optional, Union, cast
|
|
27
28
|
|
|
@@ -33,6 +34,7 @@ from flwr.cli.config_utils import get_fab_metadata
|
|
|
33
34
|
from flwr.cli.install import install_from_fab
|
|
34
35
|
from flwr.client.client import Client
|
|
35
36
|
from flwr.client.client_app import ClientApp, LoadClientAppError
|
|
37
|
+
from flwr.client.clientapp.app import flwr_clientapp
|
|
36
38
|
from flwr.client.nodestate.nodestate_factory import NodeStateFactory
|
|
37
39
|
from flwr.client.typing import ClientFnExt
|
|
38
40
|
from flwr.common import GRPC_MAX_MESSAGE_LENGTH, Context, EventType, Message, event
|
|
@@ -43,7 +45,6 @@ from flwr.common.constant import (
|
|
|
43
45
|
ISOLATION_MODE_PROCESS,
|
|
44
46
|
ISOLATION_MODE_SUBPROCESS,
|
|
45
47
|
MAX_RETRY_DELAY,
|
|
46
|
-
MISSING_EXTRA_REST,
|
|
47
48
|
RUN_ID_NUM_BYTES,
|
|
48
49
|
SERVER_OCTET,
|
|
49
50
|
TRANSPORT_TYPE_GRPC_ADAPTER,
|
|
@@ -53,13 +54,13 @@ from flwr.common.constant import (
|
|
|
53
54
|
TRANSPORT_TYPES,
|
|
54
55
|
ErrorCode,
|
|
55
56
|
)
|
|
57
|
+
from flwr.common.exit import ExitCode, flwr_exit
|
|
58
|
+
from flwr.common.grpc import generic_create_grpc_server
|
|
56
59
|
from flwr.common.logger import log, warn_deprecated_feature
|
|
57
60
|
from flwr.common.message import Error
|
|
58
61
|
from flwr.common.retry_invoker import RetryInvoker, RetryState, exponential
|
|
59
62
|
from flwr.common.typing import Fab, Run, RunNotRunningException, UserConfig
|
|
60
63
|
from flwr.proto.clientappio_pb2_grpc import add_ClientAppIoServicer_to_server
|
|
61
|
-
from flwr.server.superlink.fleet.grpc_bidi.grpc_server import generic_create_grpc_server
|
|
62
|
-
from flwr.server.superlink.linkstate.utils import generate_rand_int_from_bytes
|
|
63
64
|
|
|
64
65
|
from .clientapp.clientappio_servicer import ClientAppInputs, ClientAppIoServicer
|
|
65
66
|
from .grpc_adapter_client.connection import grpc_adapter
|
|
@@ -345,10 +346,7 @@ def start_client_internal(
|
|
|
345
346
|
transport, server_address
|
|
346
347
|
)
|
|
347
348
|
|
|
348
|
-
app_state_tracker = _AppStateTracker()
|
|
349
|
-
|
|
350
349
|
def _on_sucess(retry_state: RetryState) -> None:
|
|
351
|
-
app_state_tracker.is_connected = True
|
|
352
350
|
if retry_state.tries > 1:
|
|
353
351
|
log(
|
|
354
352
|
INFO,
|
|
@@ -358,7 +356,6 @@ def start_client_internal(
|
|
|
358
356
|
)
|
|
359
357
|
|
|
360
358
|
def _on_backoff(retry_state: RetryState) -> None:
|
|
361
|
-
app_state_tracker.is_connected = False
|
|
362
359
|
if retry_state.tries == 1:
|
|
363
360
|
log(WARN, "Connection attempt failed, retrying...")
|
|
364
361
|
else:
|
|
@@ -391,10 +388,11 @@ def start_client_internal(
|
|
|
391
388
|
run_info_store: Optional[DeprecatedRunInfoStore] = None
|
|
392
389
|
state_factory = NodeStateFactory()
|
|
393
390
|
state = state_factory.state()
|
|
391
|
+
mp_spawn_context = multiprocessing.get_context("spawn")
|
|
394
392
|
|
|
395
393
|
runs: dict[int, Run] = {}
|
|
396
394
|
|
|
397
|
-
while
|
|
395
|
+
while True:
|
|
398
396
|
sleep_duration: int = 0
|
|
399
397
|
with connection(
|
|
400
398
|
address,
|
|
@@ -433,9 +431,8 @@ def start_client_internal(
|
|
|
433
431
|
node_config=node_config,
|
|
434
432
|
)
|
|
435
433
|
|
|
436
|
-
app_state_tracker.register_signal_handler()
|
|
437
434
|
# pylint: disable=too-many-nested-blocks
|
|
438
|
-
while
|
|
435
|
+
while True:
|
|
439
436
|
try:
|
|
440
437
|
# Receive
|
|
441
438
|
message = receive()
|
|
@@ -513,7 +510,7 @@ def start_client_internal(
|
|
|
513
510
|
# Docker container.
|
|
514
511
|
|
|
515
512
|
# Generate SuperNode token
|
|
516
|
-
token
|
|
513
|
+
token = int.from_bytes(urandom(RUN_ID_NUM_BYTES), "little")
|
|
517
514
|
|
|
518
515
|
# Mode 1: SuperNode starts ClientApp as subprocess
|
|
519
516
|
start_subprocess = isolation == ISOLATION_MODE_SUBPROCESS
|
|
@@ -549,12 +546,13 @@ def start_client_internal(
|
|
|
549
546
|
]
|
|
550
547
|
command.append("--insecure")
|
|
551
548
|
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
check=True,
|
|
549
|
+
proc = mp_spawn_context.Process(
|
|
550
|
+
target=_run_flwr_clientapp,
|
|
551
|
+
args=(command, os.getpid()),
|
|
552
|
+
daemon=True,
|
|
557
553
|
)
|
|
554
|
+
proc.start()
|
|
555
|
+
proc.join()
|
|
558
556
|
else:
|
|
559
557
|
# Wait for output to become available
|
|
560
558
|
while not clientappio_servicer.has_outputs():
|
|
@@ -592,10 +590,7 @@ def start_client_internal(
|
|
|
592
590
|
e_code = ErrorCode.LOAD_CLIENT_APP_EXCEPTION
|
|
593
591
|
exc_entity = "SuperNode"
|
|
594
592
|
|
|
595
|
-
|
|
596
|
-
log(
|
|
597
|
-
ERROR, "%s raised an exception", exc_entity, exc_info=ex
|
|
598
|
-
)
|
|
593
|
+
log(ERROR, "%s raised an exception", exc_entity, exc_info=ex)
|
|
599
594
|
|
|
600
595
|
# Create error message
|
|
601
596
|
reply_message = message.create_error_reply(
|
|
@@ -621,19 +616,14 @@ def start_client_internal(
|
|
|
621
616
|
run_id,
|
|
622
617
|
)
|
|
623
618
|
log(INFO, "")
|
|
624
|
-
|
|
625
|
-
except StopIteration:
|
|
626
|
-
sleep_duration = 0
|
|
627
|
-
break
|
|
628
619
|
# pylint: enable=too-many-nested-blocks
|
|
629
620
|
|
|
630
621
|
# Unregister node
|
|
631
|
-
if delete_node is not None
|
|
622
|
+
if delete_node is not None:
|
|
632
623
|
delete_node() # pylint: disable=not-callable
|
|
633
624
|
|
|
634
625
|
if sleep_duration == 0:
|
|
635
626
|
log(INFO, "Disconnect and shut down")
|
|
636
|
-
del app_state_tracker
|
|
637
627
|
break
|
|
638
628
|
|
|
639
629
|
# Sleep and reconnect afterwards
|
|
@@ -773,7 +763,10 @@ def _init_connection(transport: Optional[str], server_address: str) -> tuple[
|
|
|
773
763
|
# Parse IP address
|
|
774
764
|
parsed_address = parse_address(server_address)
|
|
775
765
|
if not parsed_address:
|
|
776
|
-
|
|
766
|
+
flwr_exit(
|
|
767
|
+
ExitCode.COMMON_ADDRESS_INVALID,
|
|
768
|
+
f"SuperLink address ({server_address}) cannot be parsed.",
|
|
769
|
+
)
|
|
777
770
|
host, port, is_v6 = parsed_address
|
|
778
771
|
address = f"[{host}]:{port}" if is_v6 else f"{host}:{port}"
|
|
779
772
|
|
|
@@ -788,12 +781,9 @@ def _init_connection(transport: Optional[str], server_address: str) -> tuple[
|
|
|
788
781
|
|
|
789
782
|
from .rest_client.connection import http_request_response
|
|
790
783
|
except ModuleNotFoundError:
|
|
791
|
-
|
|
784
|
+
flwr_exit(ExitCode.COMMON_MISSING_EXTRA_REST)
|
|
792
785
|
if server_address[:4] != "http":
|
|
793
|
-
|
|
794
|
-
"When using the REST API, please provide `https://` or "
|
|
795
|
-
"`http://` before the server address (e.g. `http://127.0.0.1:8080`)"
|
|
796
|
-
)
|
|
786
|
+
flwr_exit(ExitCode.SUPERNODE_REST_ADDRESS_INVALID)
|
|
797
787
|
connection, error_type = http_request_response, RequestsConnectionError
|
|
798
788
|
elif transport == TRANSPORT_TYPE_GRPC_RERE:
|
|
799
789
|
connection, error_type = grpc_request_response, RpcError
|
|
@@ -809,21 +799,19 @@ def _init_connection(transport: Optional[str], server_address: str) -> tuple[
|
|
|
809
799
|
return connection, address, error_type
|
|
810
800
|
|
|
811
801
|
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
802
|
+
def _run_flwr_clientapp(args: list[str], main_pid: int) -> None:
|
|
803
|
+
# Monitor the main process in case of SIGKILL
|
|
804
|
+
def main_process_monitor() -> None:
|
|
805
|
+
while True:
|
|
806
|
+
time.sleep(1)
|
|
807
|
+
if os.getppid() != main_pid:
|
|
808
|
+
os.kill(os.getpid(), 9)
|
|
819
809
|
|
|
820
|
-
|
|
821
|
-
# pylint: disable=unused-argument
|
|
822
|
-
self.interrupt = True
|
|
823
|
-
raise StopIteration from None
|
|
810
|
+
threading.Thread(target=main_process_monitor, daemon=True).start()
|
|
824
811
|
|
|
825
|
-
|
|
826
|
-
|
|
812
|
+
# Run the command
|
|
813
|
+
sys.argv = args
|
|
814
|
+
flwr_clientapp()
|
|
827
815
|
|
|
828
816
|
|
|
829
817
|
def run_clientappio_api_grpc(
|