flwr-nightly 1.13.0.dev20241109__py3-none-any.whl → 1.13.0.dev20241112__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.
Potentially problematic release.
This version of flwr-nightly might be problematic. Click here for more details.
- flwr/cli/build.py +37 -0
- flwr/cli/install.py +5 -3
- flwr/client/clientapp/app.py +21 -16
- flwr/client/supernode/app.py +2 -36
- flwr/common/args.py +148 -0
- flwr/common/logger.py +6 -2
- flwr/server/app.py +47 -76
- flwr/server/serverapp/app.py +15 -71
- flwr/server/superlink/linkstate/in_memory_linkstate.py +5 -0
- flwr/server/superlink/linkstate/linkstate.py +5 -4
- flwr/server/superlink/linkstate/sqlite_linkstate.py +8 -2
- flwr/server/superlink/linkstate/utils.py +11 -0
- flwr/simulation/__init__.py +3 -1
- flwr/simulation/app.py +245 -352
- flwr/simulation/legacy_app.py +382 -0
- flwr/superexec/simulation.py +43 -101
- {flwr_nightly-1.13.0.dev20241109.dist-info → flwr_nightly-1.13.0.dev20241112.dist-info}/METADATA +4 -2
- {flwr_nightly-1.13.0.dev20241109.dist-info → flwr_nightly-1.13.0.dev20241112.dist-info}/RECORD +21 -19
- {flwr_nightly-1.13.0.dev20241109.dist-info → flwr_nightly-1.13.0.dev20241112.dist-info}/entry_points.txt +1 -0
- {flwr_nightly-1.13.0.dev20241109.dist-info → flwr_nightly-1.13.0.dev20241112.dist-info}/LICENSE +0 -0
- {flwr_nightly-1.13.0.dev20241109.dist-info → flwr_nightly-1.13.0.dev20241112.dist-info}/WHEEL +0 -0
flwr/cli/build.py
CHANGED
|
@@ -19,14 +19,18 @@ import os
|
|
|
19
19
|
import shutil
|
|
20
20
|
import tempfile
|
|
21
21
|
import zipfile
|
|
22
|
+
from logging import DEBUG, ERROR
|
|
22
23
|
from pathlib import Path
|
|
23
24
|
from typing import Annotated, Any, Optional, Union
|
|
24
25
|
|
|
25
26
|
import pathspec
|
|
26
27
|
import tomli_w
|
|
27
28
|
import typer
|
|
29
|
+
from hatchling.builders.wheel import WheelBuilder
|
|
30
|
+
from hatchling.metadata.core import ProjectMetadata
|
|
28
31
|
|
|
29
32
|
from flwr.common.constant import FAB_ALLOWED_EXTENSIONS, FAB_DATE, FAB_HASH_TRUNCATION
|
|
33
|
+
from flwr.common.logger import log
|
|
30
34
|
|
|
31
35
|
from .config_utils import load_and_validate
|
|
32
36
|
from .utils import is_valid_project_name
|
|
@@ -51,6 +55,27 @@ def get_fab_filename(conf: dict[str, Any], fab_hash: str) -> str:
|
|
|
51
55
|
return f"{publisher}.{name}.{version}.{fab_hash_truncated}.fab"
|
|
52
56
|
|
|
53
57
|
|
|
58
|
+
def _build_app_wheel(app: Path) -> Path:
|
|
59
|
+
"""Build app as a wheel and return its path."""
|
|
60
|
+
# Path to your project directory
|
|
61
|
+
app_dir = str(app.resolve())
|
|
62
|
+
try:
|
|
63
|
+
|
|
64
|
+
# Initialize the WheelBuilder
|
|
65
|
+
builder = WheelBuilder(
|
|
66
|
+
app_dir, metadata=ProjectMetadata(root=app_dir, plugin_manager=None)
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
# Build
|
|
70
|
+
whl_path = Path(next(builder.build(directory=app_dir)))
|
|
71
|
+
log(DEBUG, "Wheel succesfully built: %s", str(whl_path))
|
|
72
|
+
except Exception as ex:
|
|
73
|
+
log(ERROR, "Exception encountered when building wheel.", exc_info=ex)
|
|
74
|
+
raise typer.Exit(code=1) from ex
|
|
75
|
+
|
|
76
|
+
return whl_path
|
|
77
|
+
|
|
78
|
+
|
|
54
79
|
# pylint: disable=too-many-locals, too-many-statements
|
|
55
80
|
def build(
|
|
56
81
|
app: Annotated[
|
|
@@ -106,6 +131,12 @@ def build(
|
|
|
106
131
|
bold=True,
|
|
107
132
|
)
|
|
108
133
|
|
|
134
|
+
# Build wheel
|
|
135
|
+
whl_path = _build_app_wheel(app)
|
|
136
|
+
|
|
137
|
+
# Add path to .whl to `[tool.flwr.app]`
|
|
138
|
+
conf["tool"]["flwr"]["app"]["whl"] = str(whl_path.name)
|
|
139
|
+
|
|
109
140
|
# Load .gitignore rules if present
|
|
110
141
|
ignore_spec = _load_gitignore(app)
|
|
111
142
|
|
|
@@ -137,6 +168,9 @@ def build(
|
|
|
137
168
|
and f.name != "pyproject.toml" # Exclude the original pyproject.toml
|
|
138
169
|
]
|
|
139
170
|
|
|
171
|
+
# Include FAB .whl
|
|
172
|
+
all_files.append(whl_path)
|
|
173
|
+
|
|
140
174
|
for file_path in all_files:
|
|
141
175
|
# Read the file content manually
|
|
142
176
|
with open(file_path, "rb") as f:
|
|
@@ -153,6 +187,9 @@ def build(
|
|
|
153
187
|
# Add CONTENT and CONTENT.jwt to the zip file
|
|
154
188
|
write_to_zip(fab_file, ".info/CONTENT", list_file_content)
|
|
155
189
|
|
|
190
|
+
# Erase FAB .whl in app directory
|
|
191
|
+
whl_path.unlink()
|
|
192
|
+
|
|
156
193
|
# Get hash of FAB file
|
|
157
194
|
content = Path(temp_filename).read_bytes()
|
|
158
195
|
fab_hash = hashlib.sha256(content).hexdigest()
|
flwr/cli/install.py
CHANGED
|
@@ -188,23 +188,25 @@ def validate_and_install(
|
|
|
188
188
|
else:
|
|
189
189
|
shutil.copy2(item, install_dir / item.name)
|
|
190
190
|
|
|
191
|
+
whl_file = config["tool"]["flwr"]["app"]["whl"]
|
|
192
|
+
install_whl = install_dir / whl_file
|
|
191
193
|
try:
|
|
192
194
|
subprocess.run(
|
|
193
|
-
["pip", "install", "
|
|
195
|
+
["pip", "install", "--no-deps", install_whl],
|
|
194
196
|
capture_output=True,
|
|
195
197
|
text=True,
|
|
196
198
|
check=True,
|
|
197
199
|
)
|
|
198
200
|
except subprocess.CalledProcessError as e:
|
|
199
201
|
typer.secho(
|
|
200
|
-
f"❌ Failed to
|
|
202
|
+
f"❌ Failed to install {project_name}:\n{e.stderr}",
|
|
201
203
|
fg=typer.colors.RED,
|
|
202
204
|
bold=True,
|
|
203
205
|
)
|
|
204
206
|
raise typer.Exit(code=1) from e
|
|
205
207
|
|
|
206
208
|
typer.secho(
|
|
207
|
-
f"🎊 Successfully installed {project_name}
|
|
209
|
+
f"🎊 Successfully installed {project_name}.",
|
|
208
210
|
fg=typer.colors.GREEN,
|
|
209
211
|
bold=True,
|
|
210
212
|
)
|
flwr/client/clientapp/app.py
CHANGED
|
@@ -24,6 +24,8 @@ import grpc
|
|
|
24
24
|
from flwr.cli.install import install_from_fab
|
|
25
25
|
from flwr.client.client_app import ClientApp, LoadClientAppError
|
|
26
26
|
from flwr.common import Context, Message
|
|
27
|
+
from flwr.common.args import add_args_flwr_app_common
|
|
28
|
+
from flwr.common.config import get_flwr_dir
|
|
27
29
|
from flwr.common.constant import ErrorCode
|
|
28
30
|
from flwr.common.grpc import create_channel
|
|
29
31
|
from flwr.common.logger import log
|
|
@@ -60,7 +62,7 @@ def flwr_clientapp() -> None:
|
|
|
60
62
|
parser.add_argument(
|
|
61
63
|
"--supernode",
|
|
62
64
|
type=str,
|
|
63
|
-
help="Address of SuperNode ClientAppIo
|
|
65
|
+
help="Address of SuperNode's ClientAppIo API",
|
|
64
66
|
)
|
|
65
67
|
parser.add_argument(
|
|
66
68
|
"--token",
|
|
@@ -68,17 +70,24 @@ def flwr_clientapp() -> None:
|
|
|
68
70
|
required=False,
|
|
69
71
|
help="Unique token generated by SuperNode for each ClientApp execution",
|
|
70
72
|
)
|
|
73
|
+
add_args_flwr_app_common(parser=parser)
|
|
71
74
|
args = parser.parse_args()
|
|
72
75
|
|
|
73
76
|
log(INFO, "Starting Flower ClientApp")
|
|
77
|
+
|
|
74
78
|
log(
|
|
75
79
|
DEBUG,
|
|
76
|
-
"
|
|
80
|
+
"Starting isolated `ClientApp` connected to SuperNode's ClientAppIo API at %s "
|
|
77
81
|
"with token %s",
|
|
78
82
|
args.supernode,
|
|
79
83
|
args.token,
|
|
80
84
|
)
|
|
81
|
-
run_clientapp(
|
|
85
|
+
run_clientapp(
|
|
86
|
+
supernode=args.supernode,
|
|
87
|
+
run_once=(args.token is not None),
|
|
88
|
+
token=args.token,
|
|
89
|
+
flwr_dir=args.flwr_dir,
|
|
90
|
+
)
|
|
82
91
|
|
|
83
92
|
|
|
84
93
|
def on_channel_state_change(channel_connectivity: str) -> None:
|
|
@@ -88,27 +97,23 @@ def on_channel_state_change(channel_connectivity: str) -> None:
|
|
|
88
97
|
|
|
89
98
|
def run_clientapp( # pylint: disable=R0914
|
|
90
99
|
supernode: str,
|
|
100
|
+
run_once: bool,
|
|
91
101
|
token: Optional[int] = None,
|
|
102
|
+
flwr_dir: Optional[str] = None,
|
|
92
103
|
) -> None:
|
|
93
|
-
"""Run Flower ClientApp process.
|
|
94
|
-
|
|
95
|
-
Parameters
|
|
96
|
-
----------
|
|
97
|
-
supernode : str
|
|
98
|
-
Address of SuperNode
|
|
99
|
-
token : Optional[int] (default: None)
|
|
100
|
-
Unique SuperNode token for ClientApp-SuperNode authentication
|
|
101
|
-
"""
|
|
104
|
+
"""Run Flower ClientApp process."""
|
|
102
105
|
channel = create_channel(
|
|
103
106
|
server_address=supernode,
|
|
104
107
|
insecure=True,
|
|
105
108
|
)
|
|
106
109
|
channel.subscribe(on_channel_state_change)
|
|
107
110
|
|
|
111
|
+
# Resolve directory where FABs are installed
|
|
112
|
+
flwr_dir_ = get_flwr_dir(flwr_dir)
|
|
113
|
+
|
|
108
114
|
try:
|
|
109
115
|
stub = ClientAppIoStub(channel)
|
|
110
116
|
|
|
111
|
-
only_once = token is not None
|
|
112
117
|
while True:
|
|
113
118
|
# If token is not set, loop until token is received from SuperNode
|
|
114
119
|
while token is None:
|
|
@@ -121,13 +126,13 @@ def run_clientapp( # pylint: disable=R0914
|
|
|
121
126
|
# Install FAB, if provided
|
|
122
127
|
if fab:
|
|
123
128
|
log(DEBUG, "Flower ClientApp starts FAB installation.")
|
|
124
|
-
install_from_fab(fab.content, flwr_dir=
|
|
129
|
+
install_from_fab(fab.content, flwr_dir=flwr_dir_, skip_prompt=True)
|
|
125
130
|
|
|
126
131
|
load_client_app_fn = get_load_client_app_fn(
|
|
127
132
|
default_app_ref="",
|
|
128
133
|
app_path=None,
|
|
129
134
|
multi_app=True,
|
|
130
|
-
flwr_dir=
|
|
135
|
+
flwr_dir=str(flwr_dir_),
|
|
131
136
|
)
|
|
132
137
|
|
|
133
138
|
try:
|
|
@@ -169,7 +174,7 @@ def run_clientapp( # pylint: disable=R0914
|
|
|
169
174
|
|
|
170
175
|
# Stop the loop if `flwr-clientapp` is expected to process only a single
|
|
171
176
|
# message
|
|
172
|
-
if
|
|
177
|
+
if run_once:
|
|
173
178
|
break
|
|
174
179
|
|
|
175
180
|
except KeyboardInterrupt:
|
flwr/client/supernode/app.py
CHANGED
|
@@ -28,6 +28,7 @@ from cryptography.hazmat.primitives.serialization import (
|
|
|
28
28
|
)
|
|
29
29
|
|
|
30
30
|
from flwr.common import EventType, event
|
|
31
|
+
from flwr.common.args import try_obtain_root_certificates
|
|
31
32
|
from flwr.common.config import parse_config_args
|
|
32
33
|
from flwr.common.constant import (
|
|
33
34
|
FLEET_API_GRPC_RERE_DEFAULT_ADDRESS,
|
|
@@ -61,7 +62,7 @@ def run_supernode() -> None:
|
|
|
61
62
|
"Ignoring `--flwr-dir`.",
|
|
62
63
|
)
|
|
63
64
|
|
|
64
|
-
root_certificates =
|
|
65
|
+
root_certificates = try_obtain_root_certificates(args, args.superlink)
|
|
65
66
|
load_fn = get_load_client_app_fn(
|
|
66
67
|
default_app_ref="",
|
|
67
68
|
app_path=args.app,
|
|
@@ -126,41 +127,6 @@ def _warn_deprecated_server_arg(args: argparse.Namespace) -> None:
|
|
|
126
127
|
args.superlink = args.server
|
|
127
128
|
|
|
128
129
|
|
|
129
|
-
def _get_certificates(args: argparse.Namespace) -> Optional[bytes]:
|
|
130
|
-
"""Load certificates if specified in args."""
|
|
131
|
-
# Obtain certificates
|
|
132
|
-
if args.insecure:
|
|
133
|
-
if args.root_certificates is not None:
|
|
134
|
-
sys.exit(
|
|
135
|
-
"Conflicting options: The '--insecure' flag disables HTTPS, "
|
|
136
|
-
"but '--root-certificates' was also specified. Please remove "
|
|
137
|
-
"the '--root-certificates' option when running in insecure mode, "
|
|
138
|
-
"or omit '--insecure' to use HTTPS."
|
|
139
|
-
)
|
|
140
|
-
log(
|
|
141
|
-
WARN,
|
|
142
|
-
"Option `--insecure` was set. "
|
|
143
|
-
"Starting insecure HTTP client connected to %s.",
|
|
144
|
-
args.superlink,
|
|
145
|
-
)
|
|
146
|
-
root_certificates = None
|
|
147
|
-
else:
|
|
148
|
-
# Load the certificates if provided, or load the system certificates
|
|
149
|
-
cert_path = args.root_certificates
|
|
150
|
-
if cert_path is None:
|
|
151
|
-
root_certificates = None
|
|
152
|
-
else:
|
|
153
|
-
root_certificates = Path(cert_path).read_bytes()
|
|
154
|
-
log(
|
|
155
|
-
DEBUG,
|
|
156
|
-
"Starting secure HTTPS client connected to %s "
|
|
157
|
-
"with the following certificates: %s.",
|
|
158
|
-
args.superlink,
|
|
159
|
-
cert_path,
|
|
160
|
-
)
|
|
161
|
-
return root_certificates
|
|
162
|
-
|
|
163
|
-
|
|
164
130
|
def _parse_args_run_supernode() -> argparse.ArgumentParser:
|
|
165
131
|
"""Parse flower-supernode command line arguments."""
|
|
166
132
|
parser = argparse.ArgumentParser(
|
flwr/common/args.py
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# Copyright 2024 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
|
+
"""Common Flower arguments."""
|
|
16
|
+
|
|
17
|
+
import argparse
|
|
18
|
+
import sys
|
|
19
|
+
from logging import DEBUG, WARN
|
|
20
|
+
from os.path import isfile
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
from typing import Optional
|
|
23
|
+
|
|
24
|
+
from flwr.common.constant import (
|
|
25
|
+
TRANSPORT_TYPE_GRPC_ADAPTER,
|
|
26
|
+
TRANSPORT_TYPE_GRPC_RERE,
|
|
27
|
+
TRANSPORT_TYPE_REST,
|
|
28
|
+
)
|
|
29
|
+
from flwr.common.logger import log
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def add_args_flwr_app_common(parser: argparse.ArgumentParser) -> None:
|
|
33
|
+
"""Add common Flower arguments for flwr-*app to the provided parser."""
|
|
34
|
+
parser.add_argument(
|
|
35
|
+
"--flwr-dir",
|
|
36
|
+
default=None,
|
|
37
|
+
help="""The path containing installed Flower Apps.
|
|
38
|
+
By default, this value is equal to:
|
|
39
|
+
|
|
40
|
+
- `$FLWR_HOME/` if `$FLWR_HOME` is defined
|
|
41
|
+
- `$XDG_DATA_HOME/.flwr/` if `$XDG_DATA_HOME` is defined
|
|
42
|
+
- `$HOME/.flwr/` in all other cases
|
|
43
|
+
""",
|
|
44
|
+
)
|
|
45
|
+
parser.add_argument(
|
|
46
|
+
"--insecure",
|
|
47
|
+
action="store_true",
|
|
48
|
+
help="Run the server without HTTPS, regardless of whether certificate "
|
|
49
|
+
"paths are provided. By default, the server runs with HTTPS enabled. "
|
|
50
|
+
"Use this flag only if you understand the risks.",
|
|
51
|
+
)
|
|
52
|
+
parser.add_argument(
|
|
53
|
+
"--root-certificates",
|
|
54
|
+
metavar="ROOT_CERT",
|
|
55
|
+
type=str,
|
|
56
|
+
help="Specifies the path to the PEM-encoded root certificate file for "
|
|
57
|
+
"establishing secure HTTPS connections.",
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def try_obtain_root_certificates(
|
|
62
|
+
args: argparse.Namespace,
|
|
63
|
+
grpc_server_address: str,
|
|
64
|
+
) -> Optional[bytes]:
|
|
65
|
+
"""Validate and return the root certificates."""
|
|
66
|
+
root_cert_path = args.root_certificates
|
|
67
|
+
if args.insecure:
|
|
68
|
+
if root_cert_path is not None:
|
|
69
|
+
sys.exit(
|
|
70
|
+
"Conflicting options: The '--insecure' flag disables HTTPS, "
|
|
71
|
+
"but '--root-certificates' was also specified. Please remove "
|
|
72
|
+
"the '--root-certificates' option when running in insecure mode, "
|
|
73
|
+
"or omit '--insecure' to use HTTPS."
|
|
74
|
+
)
|
|
75
|
+
log(
|
|
76
|
+
WARN,
|
|
77
|
+
"Option `--insecure` was set. Starting insecure HTTP channel to %s.",
|
|
78
|
+
grpc_server_address,
|
|
79
|
+
)
|
|
80
|
+
root_certificates = None
|
|
81
|
+
else:
|
|
82
|
+
# Load the certificates if provided, or load the system certificates
|
|
83
|
+
if not isfile(root_cert_path):
|
|
84
|
+
sys.exit("Path argument `--root-certificates` does not point to a file.")
|
|
85
|
+
root_certificates = Path(root_cert_path).read_bytes()
|
|
86
|
+
log(
|
|
87
|
+
DEBUG,
|
|
88
|
+
"Starting secure HTTPS channel to %s "
|
|
89
|
+
"with the following certificates: %s.",
|
|
90
|
+
grpc_server_address,
|
|
91
|
+
root_cert_path,
|
|
92
|
+
)
|
|
93
|
+
return root_certificates
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def try_obtain_server_certificates(
|
|
97
|
+
args: argparse.Namespace,
|
|
98
|
+
transport_type: str,
|
|
99
|
+
) -> Optional[tuple[bytes, bytes, bytes]]:
|
|
100
|
+
"""Validate and return the CA cert, server cert, and server private key."""
|
|
101
|
+
if args.insecure:
|
|
102
|
+
log(WARN, "Option `--insecure` was set. Starting insecure HTTP server.")
|
|
103
|
+
return None
|
|
104
|
+
# Check if certificates are provided
|
|
105
|
+
if transport_type in [TRANSPORT_TYPE_GRPC_RERE, TRANSPORT_TYPE_GRPC_ADAPTER]:
|
|
106
|
+
if args.ssl_certfile and args.ssl_keyfile and args.ssl_ca_certfile:
|
|
107
|
+
if not isfile(args.ssl_ca_certfile):
|
|
108
|
+
sys.exit("Path argument `--ssl-ca-certfile` does not point to a file.")
|
|
109
|
+
if not isfile(args.ssl_certfile):
|
|
110
|
+
sys.exit("Path argument `--ssl-certfile` does not point to a file.")
|
|
111
|
+
if not isfile(args.ssl_keyfile):
|
|
112
|
+
sys.exit("Path argument `--ssl-keyfile` does not point to a file.")
|
|
113
|
+
certificates = (
|
|
114
|
+
Path(args.ssl_ca_certfile).read_bytes(), # CA certificate
|
|
115
|
+
Path(args.ssl_certfile).read_bytes(), # server certificate
|
|
116
|
+
Path(args.ssl_keyfile).read_bytes(), # server private key
|
|
117
|
+
)
|
|
118
|
+
return certificates
|
|
119
|
+
if args.ssl_certfile or args.ssl_keyfile or args.ssl_ca_certfile:
|
|
120
|
+
sys.exit(
|
|
121
|
+
"You need to provide valid file paths to `--ssl-certfile`, "
|
|
122
|
+
"`--ssl-keyfile`, and `—-ssl-ca-certfile` to create a secure "
|
|
123
|
+
"connection in Fleet API server (gRPC-rere)."
|
|
124
|
+
)
|
|
125
|
+
if transport_type == TRANSPORT_TYPE_REST:
|
|
126
|
+
if args.ssl_certfile and args.ssl_keyfile:
|
|
127
|
+
if not isfile(args.ssl_certfile):
|
|
128
|
+
sys.exit("Path argument `--ssl-certfile` does not point to a file.")
|
|
129
|
+
if not isfile(args.ssl_keyfile):
|
|
130
|
+
sys.exit("Path argument `--ssl-keyfile` does not point to a file.")
|
|
131
|
+
certificates = (
|
|
132
|
+
b"",
|
|
133
|
+
Path(args.ssl_certfile).read_bytes(), # server certificate
|
|
134
|
+
Path(args.ssl_keyfile).read_bytes(), # server private key
|
|
135
|
+
)
|
|
136
|
+
return certificates
|
|
137
|
+
if args.ssl_certfile or args.ssl_keyfile:
|
|
138
|
+
sys.exit(
|
|
139
|
+
"You need to provide valid file paths to `--ssl-certfile` "
|
|
140
|
+
"and `--ssl-keyfile` to create a secure connection "
|
|
141
|
+
"in Fleet API server (REST, experimental)."
|
|
142
|
+
)
|
|
143
|
+
sys.exit(
|
|
144
|
+
"Certificates are required unless running in insecure mode. "
|
|
145
|
+
"Please provide certificate paths to `--ssl-certfile`, "
|
|
146
|
+
"`--ssl-keyfile`, and `—-ssl-ca-certfile` or run the server "
|
|
147
|
+
"in insecure mode using '--insecure' if you understand the risks."
|
|
148
|
+
)
|
flwr/common/logger.py
CHANGED
|
@@ -22,13 +22,14 @@ import time
|
|
|
22
22
|
from logging import WARN, LogRecord
|
|
23
23
|
from logging.handlers import HTTPHandler
|
|
24
24
|
from queue import Empty, Queue
|
|
25
|
-
from typing import TYPE_CHECKING, Any, Optional, TextIO
|
|
25
|
+
from typing import TYPE_CHECKING, Any, Optional, TextIO, Union
|
|
26
26
|
|
|
27
27
|
import grpc
|
|
28
28
|
|
|
29
29
|
from flwr.proto.log_pb2 import PushLogsRequest # pylint: disable=E0611
|
|
30
30
|
from flwr.proto.node_pb2 import Node # pylint: disable=E0611
|
|
31
31
|
from flwr.proto.serverappio_pb2_grpc import ServerAppIoStub # pylint: disable=E0611
|
|
32
|
+
from flwr.proto.simulationio_pb2_grpc import SimulationIoStub # pylint: disable=E0611
|
|
32
33
|
|
|
33
34
|
from .constant import LOG_UPLOAD_INTERVAL
|
|
34
35
|
|
|
@@ -346,7 +347,10 @@ def _log_uploader(
|
|
|
346
347
|
|
|
347
348
|
|
|
348
349
|
def start_log_uploader(
|
|
349
|
-
log_queue: Queue[Optional[str]],
|
|
350
|
+
log_queue: Queue[Optional[str]],
|
|
351
|
+
node_id: int,
|
|
352
|
+
run_id: int,
|
|
353
|
+
stub: Union[ServerAppIoStub, SimulationIoStub],
|
|
350
354
|
) -> threading.Thread:
|
|
351
355
|
"""Start the log uploader thread and return it."""
|
|
352
356
|
thread = threading.Thread(
|
flwr/server/app.py
CHANGED
|
@@ -22,7 +22,6 @@ import sys
|
|
|
22
22
|
import threading
|
|
23
23
|
from collections.abc import Sequence
|
|
24
24
|
from logging import DEBUG, INFO, WARN
|
|
25
|
-
from os.path import isfile
|
|
26
25
|
from pathlib import Path
|
|
27
26
|
from time import sleep
|
|
28
27
|
from typing import Optional
|
|
@@ -37,6 +36,7 @@ from cryptography.hazmat.primitives.serialization import (
|
|
|
37
36
|
|
|
38
37
|
from flwr.common import GRPC_MAX_MESSAGE_LENGTH, EventType, event
|
|
39
38
|
from flwr.common.address import parse_address
|
|
39
|
+
from flwr.common.args import try_obtain_server_certificates
|
|
40
40
|
from flwr.common.config import get_flwr_dir, parse_config_args
|
|
41
41
|
from flwr.common.constant import (
|
|
42
42
|
EXEC_API_DEFAULT_ADDRESS,
|
|
@@ -215,13 +215,19 @@ def run_superlink() -> None:
|
|
|
215
215
|
|
|
216
216
|
event(EventType.RUN_SUPERLINK_ENTER)
|
|
217
217
|
|
|
218
|
+
# Warn unused options
|
|
219
|
+
if args.flwr_dir is not None:
|
|
220
|
+
log(
|
|
221
|
+
WARN, "The `--flwr-dir` option is currently not in use and will be ignored."
|
|
222
|
+
)
|
|
223
|
+
|
|
218
224
|
# Parse IP addresses
|
|
219
225
|
serverappio_address, _, _ = _format_address(args.serverappio_api_address)
|
|
220
226
|
exec_address, _, _ = _format_address(args.exec_api_address)
|
|
221
227
|
simulationio_address, _, _ = _format_address(args.simulationio_api_address)
|
|
222
228
|
|
|
223
229
|
# Obtain certificates
|
|
224
|
-
certificates =
|
|
230
|
+
certificates = try_obtain_server_certificates(args, args.fleet_api_type)
|
|
225
231
|
|
|
226
232
|
# Initialize StateFactory
|
|
227
233
|
state_factory = LinkStateFactory(args.database)
|
|
@@ -359,18 +365,23 @@ def run_superlink() -> None:
|
|
|
359
365
|
else:
|
|
360
366
|
raise ValueError(f"Unknown fleet_api_type: {args.fleet_api_type}")
|
|
361
367
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
368
|
+
if args.isolation == ISOLATION_MODE_SUBPROCESS:
|
|
369
|
+
|
|
370
|
+
address = simulationio_address if sim_exec else serverappio_address
|
|
371
|
+
cmd = "flwr-simulation" if sim_exec else "flwr-serverapp"
|
|
372
|
+
|
|
373
|
+
# Scheduler thread
|
|
374
|
+
scheduler_th = threading.Thread(
|
|
375
|
+
target=_flwr_scheduler,
|
|
376
|
+
args=(
|
|
377
|
+
state_factory,
|
|
378
|
+
address,
|
|
379
|
+
args.ssl_ca_certfile,
|
|
380
|
+
cmd,
|
|
381
|
+
),
|
|
382
|
+
)
|
|
383
|
+
scheduler_th.start()
|
|
384
|
+
bckg_threads.append(scheduler_th)
|
|
374
385
|
|
|
375
386
|
# Graceful shutdown
|
|
376
387
|
register_exit_handlers(
|
|
@@ -388,12 +399,13 @@ def run_superlink() -> None:
|
|
|
388
399
|
exec_server.wait_for_termination(timeout=1)
|
|
389
400
|
|
|
390
401
|
|
|
391
|
-
def
|
|
402
|
+
def _flwr_scheduler(
|
|
392
403
|
state_factory: LinkStateFactory,
|
|
393
|
-
|
|
404
|
+
io_api_address: str,
|
|
394
405
|
ssl_ca_certfile: Optional[str],
|
|
406
|
+
cmd: str,
|
|
395
407
|
) -> None:
|
|
396
|
-
log(DEBUG, "Started
|
|
408
|
+
log(DEBUG, "Started %s scheduler thread.", cmd)
|
|
397
409
|
|
|
398
410
|
state = state_factory.state()
|
|
399
411
|
|
|
@@ -406,14 +418,16 @@ def _flwr_serverapp_scheduler(
|
|
|
406
418
|
|
|
407
419
|
log(
|
|
408
420
|
INFO,
|
|
409
|
-
"Launching
|
|
410
|
-
|
|
421
|
+
"Launching %s subprocess. Connects to SuperLink on %s",
|
|
422
|
+
cmd,
|
|
423
|
+
io_api_address,
|
|
411
424
|
)
|
|
412
|
-
# Start
|
|
425
|
+
# Start subprocess
|
|
413
426
|
command = [
|
|
414
|
-
|
|
427
|
+
cmd,
|
|
428
|
+
"--run-once",
|
|
415
429
|
"--superlink",
|
|
416
|
-
|
|
430
|
+
io_api_address,
|
|
417
431
|
]
|
|
418
432
|
if ssl_ca_certfile:
|
|
419
433
|
command.append("--root-certificates")
|
|
@@ -526,60 +540,6 @@ def _try_setup_node_authentication(
|
|
|
526
540
|
)
|
|
527
541
|
|
|
528
542
|
|
|
529
|
-
def _try_obtain_certificates(
|
|
530
|
-
args: argparse.Namespace,
|
|
531
|
-
) -> Optional[tuple[bytes, bytes, bytes]]:
|
|
532
|
-
# Obtain certificates
|
|
533
|
-
if args.insecure:
|
|
534
|
-
log(WARN, "Option `--insecure` was set. Starting insecure HTTP server.")
|
|
535
|
-
return None
|
|
536
|
-
# Check if certificates are provided
|
|
537
|
-
if args.fleet_api_type in [TRANSPORT_TYPE_GRPC_RERE, TRANSPORT_TYPE_GRPC_ADAPTER]:
|
|
538
|
-
if args.ssl_certfile and args.ssl_keyfile and args.ssl_ca_certfile:
|
|
539
|
-
if not isfile(args.ssl_ca_certfile):
|
|
540
|
-
sys.exit("Path argument `--ssl-ca-certfile` does not point to a file.")
|
|
541
|
-
if not isfile(args.ssl_certfile):
|
|
542
|
-
sys.exit("Path argument `--ssl-certfile` does not point to a file.")
|
|
543
|
-
if not isfile(args.ssl_keyfile):
|
|
544
|
-
sys.exit("Path argument `--ssl-keyfile` does not point to a file.")
|
|
545
|
-
certificates = (
|
|
546
|
-
Path(args.ssl_ca_certfile).read_bytes(), # CA certificate
|
|
547
|
-
Path(args.ssl_certfile).read_bytes(), # server certificate
|
|
548
|
-
Path(args.ssl_keyfile).read_bytes(), # server private key
|
|
549
|
-
)
|
|
550
|
-
return certificates
|
|
551
|
-
if args.ssl_certfile or args.ssl_keyfile or args.ssl_ca_certfile:
|
|
552
|
-
sys.exit(
|
|
553
|
-
"You need to provide valid file paths to `--ssl-certfile`, "
|
|
554
|
-
"`--ssl-keyfile`, and `—-ssl-ca-certfile` to create a secure "
|
|
555
|
-
"connection in Fleet API server (gRPC-rere)."
|
|
556
|
-
)
|
|
557
|
-
if args.fleet_api_type == TRANSPORT_TYPE_REST:
|
|
558
|
-
if args.ssl_certfile and args.ssl_keyfile:
|
|
559
|
-
if not isfile(args.ssl_certfile):
|
|
560
|
-
sys.exit("Path argument `--ssl-certfile` does not point to a file.")
|
|
561
|
-
if not isfile(args.ssl_keyfile):
|
|
562
|
-
sys.exit("Path argument `--ssl-keyfile` does not point to a file.")
|
|
563
|
-
certificates = (
|
|
564
|
-
b"",
|
|
565
|
-
Path(args.ssl_certfile).read_bytes(), # server certificate
|
|
566
|
-
Path(args.ssl_keyfile).read_bytes(), # server private key
|
|
567
|
-
)
|
|
568
|
-
return certificates
|
|
569
|
-
if args.ssl_certfile or args.ssl_keyfile:
|
|
570
|
-
sys.exit(
|
|
571
|
-
"You need to provide valid file paths to `--ssl-certfile` "
|
|
572
|
-
"and `--ssl-keyfile` to create a secure connection "
|
|
573
|
-
"in Fleet API server (REST, experimental)."
|
|
574
|
-
)
|
|
575
|
-
sys.exit(
|
|
576
|
-
"Certificates are required unless running in insecure mode. "
|
|
577
|
-
"Please provide certificate paths to `--ssl-certfile`, "
|
|
578
|
-
"`--ssl-keyfile`, and `—-ssl-ca-certfile` or run the server "
|
|
579
|
-
"in insecure mode using '--insecure' if you understand the risks."
|
|
580
|
-
)
|
|
581
|
-
|
|
582
|
-
|
|
583
543
|
def _run_fleet_api_grpc_rere(
|
|
584
544
|
address: str,
|
|
585
545
|
state_factory: LinkStateFactory,
|
|
@@ -694,6 +654,17 @@ def _add_args_common(parser: argparse.ArgumentParser) -> None:
|
|
|
694
654
|
"paths are provided. By default, the server runs with HTTPS enabled. "
|
|
695
655
|
"Use this flag only if you understand the risks.",
|
|
696
656
|
)
|
|
657
|
+
parser.add_argument(
|
|
658
|
+
"--flwr-dir",
|
|
659
|
+
default=None,
|
|
660
|
+
help="""The path containing installed Flower Apps.
|
|
661
|
+
The default directory is:
|
|
662
|
+
|
|
663
|
+
- `$FLWR_HOME/` if `$FLWR_HOME` is defined
|
|
664
|
+
- `$XDG_DATA_HOME/.flwr/` if `$XDG_DATA_HOME` is defined
|
|
665
|
+
- `$HOME/.flwr/` in all other cases
|
|
666
|
+
""",
|
|
667
|
+
)
|
|
697
668
|
parser.add_argument(
|
|
698
669
|
"--ssl-certfile",
|
|
699
670
|
help="Fleet API server SSL certificate file (as a path str) "
|