flwr-nightly 1.13.0.dev20241111__py3-none-any.whl → 1.14.0.dev20241126__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/app.py +2 -0
- flwr/cli/install.py +0 -16
- flwr/cli/ls.py +228 -0
- flwr/cli/new/new.py +23 -13
- flwr/cli/new/templates/app/README.md.tpl +11 -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.sklearn.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl +1 -1
- flwr/cli/run/run.py +4 -2
- flwr/client/app.py +50 -14
- flwr/client/clientapp/app.py +40 -23
- flwr/client/grpc_rere_client/connection.py +7 -12
- flwr/client/rest_client/connection.py +4 -14
- flwr/client/supernode/app.py +31 -53
- flwr/common/args.py +85 -16
- flwr/common/constant.py +24 -6
- flwr/common/date.py +18 -0
- flwr/common/grpc.py +4 -1
- flwr/common/serde.py +10 -0
- flwr/common/typing.py +31 -10
- flwr/proto/exec_pb2.py +22 -13
- flwr/proto/exec_pb2.pyi +44 -0
- flwr/proto/exec_pb2_grpc.py +34 -0
- flwr/proto/exec_pb2_grpc.pyi +13 -0
- flwr/proto/run_pb2.py +30 -30
- flwr/proto/run_pb2.pyi +18 -1
- flwr/server/app.py +47 -77
- flwr/server/driver/grpc_driver.py +66 -16
- flwr/server/run_serverapp.py +8 -238
- flwr/server/serverapp/app.py +49 -29
- flwr/server/superlink/fleet/rest_rere/rest_api.py +10 -9
- flwr/server/superlink/linkstate/in_memory_linkstate.py +71 -46
- flwr/server/superlink/linkstate/linkstate.py +19 -5
- flwr/server/superlink/linkstate/sqlite_linkstate.py +81 -113
- flwr/server/superlink/linkstate/utils.py +193 -3
- flwr/simulation/app.py +52 -91
- flwr/simulation/legacy_app.py +21 -1
- flwr/simulation/run_simulation.py +7 -18
- flwr/simulation/simulationio_connection.py +2 -2
- flwr/superexec/deployment.py +12 -6
- flwr/superexec/exec_servicer.py +31 -2
- flwr/superexec/simulation.py +11 -46
- {flwr_nightly-1.13.0.dev20241111.dist-info → flwr_nightly-1.14.0.dev20241126.dist-info}/METADATA +5 -4
- {flwr_nightly-1.13.0.dev20241111.dist-info → flwr_nightly-1.14.0.dev20241126.dist-info}/RECORD +53 -52
- {flwr_nightly-1.13.0.dev20241111.dist-info → flwr_nightly-1.14.0.dev20241126.dist-info}/LICENSE +0 -0
- {flwr_nightly-1.13.0.dev20241111.dist-info → flwr_nightly-1.14.0.dev20241126.dist-info}/WHEEL +0 -0
- {flwr_nightly-1.13.0.dev20241111.dist-info → flwr_nightly-1.14.0.dev20241126.dist-info}/entry_points.txt +0 -0
|
@@ -17,20 +17,17 @@
|
|
|
17
17
|
import time
|
|
18
18
|
import warnings
|
|
19
19
|
from collections.abc import Iterable
|
|
20
|
-
from logging import DEBUG, WARNING
|
|
21
|
-
from typing import Optional, cast
|
|
20
|
+
from logging import DEBUG, INFO, WARN, WARNING
|
|
21
|
+
from typing import Any, Optional, cast
|
|
22
22
|
|
|
23
23
|
import grpc
|
|
24
24
|
|
|
25
25
|
from flwr.common import DEFAULT_TTL, Message, Metadata, RecordSet
|
|
26
|
-
from flwr.common.constant import
|
|
26
|
+
from flwr.common.constant import MAX_RETRY_DELAY, SERVERAPPIO_API_DEFAULT_CLIENT_ADDRESS
|
|
27
27
|
from flwr.common.grpc import create_channel
|
|
28
28
|
from flwr.common.logger import log
|
|
29
|
-
from flwr.common.
|
|
30
|
-
|
|
31
|
-
message_to_taskins,
|
|
32
|
-
user_config_from_proto,
|
|
33
|
-
)
|
|
29
|
+
from flwr.common.retry_invoker import RetryInvoker, RetryState, exponential
|
|
30
|
+
from flwr.common.serde import message_from_taskres, message_to_taskins, run_from_proto
|
|
34
31
|
from flwr.common.typing import Run
|
|
35
32
|
from flwr.proto.node_pb2 import Node # pylint: disable=E0611
|
|
36
33
|
from flwr.proto.run_pb2 import GetRunRequest, GetRunResponse # pylint: disable=E0611
|
|
@@ -70,7 +67,7 @@ class GrpcDriver(Driver):
|
|
|
70
67
|
|
|
71
68
|
def __init__( # pylint: disable=too-many-arguments
|
|
72
69
|
self,
|
|
73
|
-
serverappio_service_address: str =
|
|
70
|
+
serverappio_service_address: str = SERVERAPPIO_API_DEFAULT_CLIENT_ADDRESS,
|
|
74
71
|
root_certificates: Optional[bytes] = None,
|
|
75
72
|
) -> None:
|
|
76
73
|
self._addr = serverappio_service_address
|
|
@@ -79,6 +76,7 @@ class GrpcDriver(Driver):
|
|
|
79
76
|
self._grpc_stub: Optional[ServerAppIoStub] = None
|
|
80
77
|
self._channel: Optional[grpc.Channel] = None
|
|
81
78
|
self.node = Node(node_id=0, anonymous=True)
|
|
79
|
+
self._retry_invoker = _make_simple_grpc_retry_invoker()
|
|
82
80
|
|
|
83
81
|
@property
|
|
84
82
|
def _is_connected(self) -> bool:
|
|
@@ -99,6 +97,7 @@ class GrpcDriver(Driver):
|
|
|
99
97
|
root_certificates=self._cert,
|
|
100
98
|
)
|
|
101
99
|
self._grpc_stub = ServerAppIoStub(self._channel)
|
|
100
|
+
_wrap_stub(self._grpc_stub, self._retry_invoker)
|
|
102
101
|
log(DEBUG, "[Driver] Connected to %s", self._addr)
|
|
103
102
|
|
|
104
103
|
def _disconnect(self) -> None:
|
|
@@ -119,13 +118,7 @@ class GrpcDriver(Driver):
|
|
|
119
118
|
res: GetRunResponse = self._stub.GetRun(req)
|
|
120
119
|
if not res.HasField("run"):
|
|
121
120
|
raise RuntimeError(f"Cannot find the run with ID: {run_id}")
|
|
122
|
-
self._run =
|
|
123
|
-
run_id=res.run.run_id,
|
|
124
|
-
fab_id=res.run.fab_id,
|
|
125
|
-
fab_version=res.run.fab_version,
|
|
126
|
-
fab_hash=res.run.fab_hash,
|
|
127
|
-
override_config=user_config_from_proto(res.run.override_config),
|
|
128
|
-
)
|
|
121
|
+
self._run = run_from_proto(res.run)
|
|
129
122
|
|
|
130
123
|
@property
|
|
131
124
|
def run(self) -> Run:
|
|
@@ -265,3 +258,60 @@ class GrpcDriver(Driver):
|
|
|
265
258
|
return
|
|
266
259
|
# Disconnect
|
|
267
260
|
self._disconnect()
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def _make_simple_grpc_retry_invoker() -> RetryInvoker:
|
|
264
|
+
"""Create a simple gRPC retry invoker."""
|
|
265
|
+
|
|
266
|
+
def _on_sucess(retry_state: RetryState) -> None:
|
|
267
|
+
if retry_state.tries > 1:
|
|
268
|
+
log(
|
|
269
|
+
INFO,
|
|
270
|
+
"Connection successful after %.2f seconds and %s tries.",
|
|
271
|
+
retry_state.elapsed_time,
|
|
272
|
+
retry_state.tries,
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
def _on_backoff(retry_state: RetryState) -> None:
|
|
276
|
+
if retry_state.tries == 1:
|
|
277
|
+
log(WARN, "Connection attempt failed, retrying...")
|
|
278
|
+
else:
|
|
279
|
+
log(
|
|
280
|
+
WARN,
|
|
281
|
+
"Connection attempt failed, retrying in %.2f seconds",
|
|
282
|
+
retry_state.actual_wait,
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
def _on_giveup(retry_state: RetryState) -> None:
|
|
286
|
+
if retry_state.tries > 1:
|
|
287
|
+
log(
|
|
288
|
+
WARN,
|
|
289
|
+
"Giving up reconnection after %.2f seconds and %s tries.",
|
|
290
|
+
retry_state.elapsed_time,
|
|
291
|
+
retry_state.tries,
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
return RetryInvoker(
|
|
295
|
+
wait_gen_factory=lambda: exponential(max_delay=MAX_RETRY_DELAY),
|
|
296
|
+
recoverable_exceptions=grpc.RpcError,
|
|
297
|
+
max_tries=None,
|
|
298
|
+
max_time=None,
|
|
299
|
+
on_success=_on_sucess,
|
|
300
|
+
on_backoff=_on_backoff,
|
|
301
|
+
on_giveup=_on_giveup,
|
|
302
|
+
should_giveup=lambda e: e.code() != grpc.StatusCode.UNAVAILABLE, # type: ignore
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
def _wrap_stub(stub: ServerAppIoStub, retry_invoker: RetryInvoker) -> None:
|
|
307
|
+
"""Wrap the gRPC stub with a retry invoker."""
|
|
308
|
+
|
|
309
|
+
def make_lambda(original_method: Any) -> Any:
|
|
310
|
+
return lambda *args, **kwargs: retry_invoker.invoke(
|
|
311
|
+
original_method, *args, **kwargs
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
for method_name in vars(stub):
|
|
315
|
+
method = getattr(stub, method_name)
|
|
316
|
+
if callable(method):
|
|
317
|
+
setattr(stub, method_name, make_lambda(method))
|
flwr/server/run_serverapp.py
CHANGED
|
@@ -15,33 +15,15 @@
|
|
|
15
15
|
"""Run ServerApp."""
|
|
16
16
|
|
|
17
17
|
|
|
18
|
-
import argparse
|
|
19
18
|
import sys
|
|
20
|
-
from logging import DEBUG,
|
|
21
|
-
from pathlib import Path
|
|
19
|
+
from logging import DEBUG, ERROR
|
|
22
20
|
from typing import Optional
|
|
23
21
|
|
|
24
|
-
from flwr.
|
|
25
|
-
from flwr.
|
|
26
|
-
from flwr.common import Context, EventType, RecordSet, event
|
|
27
|
-
from flwr.common.config import (
|
|
28
|
-
get_flwr_dir,
|
|
29
|
-
get_fused_config_from_dir,
|
|
30
|
-
get_metadata_from_config,
|
|
31
|
-
get_project_config,
|
|
32
|
-
get_project_dir,
|
|
33
|
-
)
|
|
34
|
-
from flwr.common.constant import SERVERAPPIO_API_DEFAULT_ADDRESS
|
|
35
|
-
from flwr.common.logger import log, update_console_handler, warn_deprecated_feature
|
|
22
|
+
from flwr.common import Context
|
|
23
|
+
from flwr.common.logger import log, warn_unsupported_feature
|
|
36
24
|
from flwr.common.object_ref import load_app
|
|
37
|
-
from flwr.proto.fab_pb2 import GetFabRequest, GetFabResponse # pylint: disable=E0611
|
|
38
|
-
from flwr.proto.run_pb2 import ( # pylint: disable=E0611
|
|
39
|
-
CreateRunRequest,
|
|
40
|
-
CreateRunResponse,
|
|
41
|
-
)
|
|
42
25
|
|
|
43
26
|
from .driver import Driver
|
|
44
|
-
from .driver.grpc_driver import GrpcDriver
|
|
45
27
|
from .server_app import LoadServerAppError, ServerApp
|
|
46
28
|
|
|
47
29
|
|
|
@@ -87,221 +69,9 @@ def run(
|
|
|
87
69
|
# pylint: disable-next=too-many-branches,too-many-statements,too-many-locals
|
|
88
70
|
def run_server_app() -> None:
|
|
89
71
|
"""Run Flower server app."""
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
# Check if the server app reference is passed.
|
|
95
|
-
# Since Flower 1.11, passing a reference is not allowed.
|
|
96
|
-
app_path: Optional[str] = args.app
|
|
97
|
-
# If the provided app_path doesn't exist, and contains a ":",
|
|
98
|
-
# it is likely to be a server app reference instead of a path.
|
|
99
|
-
if app_path is not None and not Path(app_path).exists() and ":" in app_path:
|
|
100
|
-
sys.exit(
|
|
101
|
-
"It appears you've passed a reference like `server:app`.\n\n"
|
|
102
|
-
"Note that since version `1.11.0`, `flower-server-app` no longer supports "
|
|
103
|
-
"passing a reference to a `ServerApp` attribute. Instead, you need to pass "
|
|
104
|
-
"the path to Flower app via the argument `--app`. This is the path to a "
|
|
105
|
-
"directory containing a `pyproject.toml`. You can create a valid Flower "
|
|
106
|
-
"app by executing `flwr new` and following the prompt."
|
|
107
|
-
)
|
|
108
|
-
|
|
109
|
-
if args.server != SERVERAPPIO_API_DEFAULT_ADDRESS:
|
|
110
|
-
warn = "Passing flag --server is deprecated. Use --superlink instead."
|
|
111
|
-
warn_deprecated_feature(warn)
|
|
112
|
-
|
|
113
|
-
if args.superlink != SERVERAPPIO_API_DEFAULT_ADDRESS:
|
|
114
|
-
# if `--superlink` also passed, then
|
|
115
|
-
# warn user that this argument overrides what was passed with `--server`
|
|
116
|
-
log(
|
|
117
|
-
WARN,
|
|
118
|
-
"Both `--server` and `--superlink` were passed. "
|
|
119
|
-
"`--server` will be ignored. Connecting to the "
|
|
120
|
-
"SuperLink ServerAppIo API at %s.",
|
|
121
|
-
args.superlink,
|
|
122
|
-
)
|
|
123
|
-
else:
|
|
124
|
-
args.superlink = args.server
|
|
125
|
-
|
|
126
|
-
update_console_handler(
|
|
127
|
-
level=DEBUG if args.verbose else INFO,
|
|
128
|
-
timestamps=args.verbose,
|
|
129
|
-
colored=True,
|
|
130
|
-
)
|
|
131
|
-
|
|
132
|
-
# Obtain certificates
|
|
133
|
-
if args.insecure:
|
|
134
|
-
if args.root_certificates is not None:
|
|
135
|
-
sys.exit(
|
|
136
|
-
"Conflicting options: The '--insecure' flag disables HTTPS, "
|
|
137
|
-
"but '--root-certificates' was also specified. Please remove "
|
|
138
|
-
"the '--root-certificates' option when running in insecure mode, "
|
|
139
|
-
"or omit '--insecure' to use HTTPS."
|
|
140
|
-
)
|
|
141
|
-
log(
|
|
142
|
-
WARN,
|
|
143
|
-
"Option `--insecure` was set. "
|
|
144
|
-
"Starting insecure HTTP client connected to %s.",
|
|
145
|
-
args.superlink,
|
|
146
|
-
)
|
|
147
|
-
root_certificates = None
|
|
148
|
-
else:
|
|
149
|
-
# Load the certificates if provided, or load the system certificates
|
|
150
|
-
cert_path = args.root_certificates
|
|
151
|
-
if cert_path is None:
|
|
152
|
-
root_certificates = None
|
|
153
|
-
else:
|
|
154
|
-
root_certificates = Path(cert_path).read_bytes()
|
|
155
|
-
log(
|
|
156
|
-
DEBUG,
|
|
157
|
-
"Starting secure HTTPS client connected to %s "
|
|
158
|
-
"with the following certificates: %s.",
|
|
159
|
-
args.superlink,
|
|
160
|
-
cert_path,
|
|
161
|
-
)
|
|
162
|
-
|
|
163
|
-
if not (app_path is None) ^ (args.run_id is None):
|
|
164
|
-
raise sys.exit(
|
|
165
|
-
"Please provide either a Flower App path or a Run ID, but not both. "
|
|
166
|
-
"For more details, use: ``flower-server-app -h``"
|
|
167
|
-
)
|
|
168
|
-
|
|
169
|
-
# Initialize GrpcDriver
|
|
170
|
-
if app_path is None:
|
|
171
|
-
# User provided `--run-id`, but not `app_dir`
|
|
172
|
-
driver = GrpcDriver(
|
|
173
|
-
serverappio_service_address=args.superlink,
|
|
174
|
-
root_certificates=root_certificates,
|
|
175
|
-
)
|
|
176
|
-
flwr_dir = get_flwr_dir(args.flwr_dir)
|
|
177
|
-
driver.set_run(args.run_id)
|
|
178
|
-
run_ = driver.run
|
|
179
|
-
if not run_.fab_hash:
|
|
180
|
-
raise ValueError("FAB hash not provided.")
|
|
181
|
-
fab_req = GetFabRequest(hash_str=run_.fab_hash)
|
|
182
|
-
# pylint: disable-next=W0212
|
|
183
|
-
fab_res: GetFabResponse = driver._stub.GetFab(fab_req)
|
|
184
|
-
if fab_res.fab.hash_str != run_.fab_hash:
|
|
185
|
-
raise ValueError("FAB hashes don't match.")
|
|
186
|
-
install_from_fab(fab_res.fab.content, flwr_dir, True)
|
|
187
|
-
fab_id, fab_version = get_fab_metadata(fab_res.fab.content)
|
|
188
|
-
|
|
189
|
-
app_path = str(get_project_dir(fab_id, fab_version, run_.fab_hash, flwr_dir))
|
|
190
|
-
config = get_project_config(app_path)
|
|
191
|
-
run_id = run_.run_id
|
|
192
|
-
else:
|
|
193
|
-
# User provided `app_dir`, but not `--run-id`
|
|
194
|
-
# Create run if run_id is not provided
|
|
195
|
-
driver = GrpcDriver(
|
|
196
|
-
serverappio_service_address=args.superlink,
|
|
197
|
-
root_certificates=root_certificates,
|
|
198
|
-
)
|
|
199
|
-
# Load config from the project directory
|
|
200
|
-
config = get_project_config(app_path)
|
|
201
|
-
fab_version, fab_id = get_metadata_from_config(config)
|
|
202
|
-
|
|
203
|
-
# Create run
|
|
204
|
-
req = CreateRunRequest(fab_id=fab_id, fab_version=fab_version)
|
|
205
|
-
res: CreateRunResponse = driver._stub.CreateRun(req) # pylint: disable=W0212
|
|
206
|
-
# Fetch full `Run` using `run_id`
|
|
207
|
-
driver.set_run(res.run_id) # pylint: disable=W0212
|
|
208
|
-
run_id = res.run_id
|
|
209
|
-
|
|
210
|
-
# Obtain server app reference and the run config
|
|
211
|
-
server_app_attr = config["tool"]["flwr"]["app"]["components"]["serverapp"]
|
|
212
|
-
server_app_run_config = get_fused_config_from_dir(
|
|
213
|
-
Path(app_path), driver.run.override_config
|
|
72
|
+
warn_unsupported_feature(
|
|
73
|
+
"The command `flower-server-app` is deprecated and no longer in use. "
|
|
74
|
+
"Use the `flwr-serverapp` exclusively instead."
|
|
214
75
|
)
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
log(
|
|
219
|
-
DEBUG,
|
|
220
|
-
"root_certificates: `%s`",
|
|
221
|
-
root_certificates,
|
|
222
|
-
)
|
|
223
|
-
|
|
224
|
-
# Initialize Context
|
|
225
|
-
context = Context(
|
|
226
|
-
run_id=run_id,
|
|
227
|
-
node_id=0,
|
|
228
|
-
node_config={},
|
|
229
|
-
state=RecordSet(),
|
|
230
|
-
run_config=server_app_run_config,
|
|
231
|
-
)
|
|
232
|
-
|
|
233
|
-
# Run the ServerApp with the Driver
|
|
234
|
-
run(
|
|
235
|
-
driver=driver,
|
|
236
|
-
context=context,
|
|
237
|
-
server_app_dir=app_path,
|
|
238
|
-
server_app_attr=server_app_attr,
|
|
239
|
-
)
|
|
240
|
-
|
|
241
|
-
# Clean up
|
|
242
|
-
driver.close()
|
|
243
|
-
|
|
244
|
-
event(EventType.RUN_SERVER_APP_LEAVE)
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
def _parse_args_run_server_app() -> argparse.ArgumentParser:
|
|
248
|
-
"""Parse flower-server-app command line arguments."""
|
|
249
|
-
parser = argparse.ArgumentParser(
|
|
250
|
-
description="Start a Flower server app",
|
|
251
|
-
)
|
|
252
|
-
|
|
253
|
-
parser.add_argument(
|
|
254
|
-
"app",
|
|
255
|
-
nargs="?",
|
|
256
|
-
default=None,
|
|
257
|
-
help="Load and run the `ServerApp` from the specified Flower App path. "
|
|
258
|
-
"The `pyproject.toml` file must be located in the root of this path.",
|
|
259
|
-
)
|
|
260
|
-
parser.add_argument(
|
|
261
|
-
"--insecure",
|
|
262
|
-
action="store_true",
|
|
263
|
-
help="Run the `ServerApp` without HTTPS. By default, the app runs with "
|
|
264
|
-
"HTTPS enabled. Use this flag only if you understand the risks.",
|
|
265
|
-
)
|
|
266
|
-
parser.add_argument(
|
|
267
|
-
"--verbose",
|
|
268
|
-
action="store_true",
|
|
269
|
-
help="Set the logging to `DEBUG`.",
|
|
270
|
-
)
|
|
271
|
-
parser.add_argument(
|
|
272
|
-
"--root-certificates",
|
|
273
|
-
metavar="ROOT_CERT",
|
|
274
|
-
type=str,
|
|
275
|
-
help="Specifies the path to the PEM-encoded root certificate file for "
|
|
276
|
-
"establishing secure HTTPS connections.",
|
|
277
|
-
)
|
|
278
|
-
parser.add_argument(
|
|
279
|
-
"--server",
|
|
280
|
-
default=SERVERAPPIO_API_DEFAULT_ADDRESS,
|
|
281
|
-
help="Server address",
|
|
282
|
-
)
|
|
283
|
-
parser.add_argument(
|
|
284
|
-
"--superlink",
|
|
285
|
-
default=SERVERAPPIO_API_DEFAULT_ADDRESS,
|
|
286
|
-
help="SuperLink ServerAppIo API (gRPC-rere) address "
|
|
287
|
-
"(IPv4, IPv6, or a domain name)",
|
|
288
|
-
)
|
|
289
|
-
parser.add_argument(
|
|
290
|
-
"--run-id",
|
|
291
|
-
default=None,
|
|
292
|
-
type=int,
|
|
293
|
-
help="The identifier of the run.",
|
|
294
|
-
)
|
|
295
|
-
parser.add_argument(
|
|
296
|
-
"--flwr-dir",
|
|
297
|
-
default=None,
|
|
298
|
-
help="""The path containing installed Flower Apps.
|
|
299
|
-
By default, this value is equal to:
|
|
300
|
-
|
|
301
|
-
- `$FLWR_HOME/` if `$FLWR_HOME` is defined
|
|
302
|
-
- `$XDG_DATA_HOME/.flwr/` if `$XDG_DATA_HOME` is defined
|
|
303
|
-
- `$HOME/.flwr/` in all other cases
|
|
304
|
-
""",
|
|
305
|
-
)
|
|
306
|
-
|
|
307
|
-
return parser
|
|
76
|
+
log(ERROR, "`flower-server-app` used.")
|
|
77
|
+
sys.exit()
|
flwr/server/serverapp/app.py
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
"""Flower ServerApp process."""
|
|
16
16
|
|
|
17
17
|
import argparse
|
|
18
|
+
import sys
|
|
18
19
|
from logging import DEBUG, ERROR, INFO
|
|
19
20
|
from pathlib import Path
|
|
20
21
|
from queue import Queue
|
|
@@ -23,14 +24,18 @@ from typing import Optional
|
|
|
23
24
|
|
|
24
25
|
from flwr.cli.config_utils import get_fab_metadata
|
|
25
26
|
from flwr.cli.install import install_from_fab
|
|
26
|
-
from flwr.common.args import add_args_flwr_app_common
|
|
27
|
+
from flwr.common.args import add_args_flwr_app_common
|
|
27
28
|
from flwr.common.config import (
|
|
28
29
|
get_flwr_dir,
|
|
29
30
|
get_fused_config_from_dir,
|
|
30
31
|
get_project_config,
|
|
31
32
|
get_project_dir,
|
|
32
33
|
)
|
|
33
|
-
from flwr.common.constant import
|
|
34
|
+
from flwr.common.constant import (
|
|
35
|
+
SERVERAPPIO_API_DEFAULT_CLIENT_ADDRESS,
|
|
36
|
+
Status,
|
|
37
|
+
SubStatus,
|
|
38
|
+
)
|
|
34
39
|
from flwr.common.logger import (
|
|
35
40
|
log,
|
|
36
41
|
mirror_output_to_queue,
|
|
@@ -62,37 +67,29 @@ def flwr_serverapp() -> None:
|
|
|
62
67
|
log_queue: Queue[Optional[str]] = Queue()
|
|
63
68
|
mirror_output_to_queue(log_queue)
|
|
64
69
|
|
|
65
|
-
|
|
66
|
-
description="Run a Flower ServerApp",
|
|
67
|
-
)
|
|
68
|
-
parser.add_argument(
|
|
69
|
-
"--superlink",
|
|
70
|
-
type=str,
|
|
71
|
-
help="Address of SuperLink's ServerAppIo API",
|
|
72
|
-
)
|
|
73
|
-
parser.add_argument(
|
|
74
|
-
"--run-once",
|
|
75
|
-
action="store_true",
|
|
76
|
-
help="When set, this process will start a single ServerApp for a pending Run. "
|
|
77
|
-
"If there is no pending Run, the process will exit.",
|
|
78
|
-
)
|
|
79
|
-
add_args_flwr_app_common(parser=parser)
|
|
80
|
-
args = parser.parse_args()
|
|
70
|
+
args = _parse_args_run_flwr_serverapp().parse_args()
|
|
81
71
|
|
|
82
72
|
log(INFO, "Starting Flower ServerApp")
|
|
83
|
-
|
|
73
|
+
|
|
74
|
+
if not args.insecure:
|
|
75
|
+
log(
|
|
76
|
+
ERROR,
|
|
77
|
+
"`flwr-serverapp` does not support TLS yet. "
|
|
78
|
+
"Please use the '--insecure' flag.",
|
|
79
|
+
)
|
|
80
|
+
sys.exit(1)
|
|
84
81
|
|
|
85
82
|
log(
|
|
86
83
|
DEBUG,
|
|
87
84
|
"Starting isolated `ServerApp` connected to SuperLink's ServerAppIo API at %s",
|
|
88
|
-
args.
|
|
85
|
+
args.serverappio_api_address,
|
|
89
86
|
)
|
|
90
87
|
run_serverapp(
|
|
91
|
-
|
|
88
|
+
serverappio_api_address=args.serverappio_api_address,
|
|
92
89
|
log_queue=log_queue,
|
|
93
90
|
run_once=args.run_once,
|
|
94
91
|
flwr_dir=args.flwr_dir,
|
|
95
|
-
certificates=
|
|
92
|
+
certificates=None,
|
|
96
93
|
)
|
|
97
94
|
|
|
98
95
|
# Restore stdout/stderr
|
|
@@ -100,7 +97,7 @@ def flwr_serverapp() -> None:
|
|
|
100
97
|
|
|
101
98
|
|
|
102
99
|
def run_serverapp( # pylint: disable=R0914, disable=W0212
|
|
103
|
-
|
|
100
|
+
serverappio_api_address: str,
|
|
104
101
|
log_queue: Queue[Optional[str]],
|
|
105
102
|
run_once: bool,
|
|
106
103
|
flwr_dir: Optional[str] = None,
|
|
@@ -108,7 +105,7 @@ def run_serverapp( # pylint: disable=R0914, disable=W0212
|
|
|
108
105
|
) -> None:
|
|
109
106
|
"""Run Flower ServerApp process."""
|
|
110
107
|
driver = GrpcDriver(
|
|
111
|
-
serverappio_service_address=
|
|
108
|
+
serverappio_service_address=serverappio_api_address,
|
|
112
109
|
root_certificates=certificates,
|
|
113
110
|
)
|
|
114
111
|
|
|
@@ -196,6 +193,12 @@ def run_serverapp( # pylint: disable=R0914, disable=W0212
|
|
|
196
193
|
run_status = RunStatus(Status.FINISHED, SubStatus.FAILED, str(ex))
|
|
197
194
|
|
|
198
195
|
finally:
|
|
196
|
+
# Stop log uploader for this run and upload final logs
|
|
197
|
+
if log_uploader:
|
|
198
|
+
stop_log_uploader(log_queue, log_uploader)
|
|
199
|
+
log_uploader = None
|
|
200
|
+
|
|
201
|
+
# Update run status
|
|
199
202
|
if run_status:
|
|
200
203
|
run_status_proto = run_status_to_proto(run_status)
|
|
201
204
|
driver._stub.UpdateRunStatus(
|
|
@@ -204,11 +207,28 @@ def run_serverapp( # pylint: disable=R0914, disable=W0212
|
|
|
204
207
|
)
|
|
205
208
|
)
|
|
206
209
|
|
|
207
|
-
# Stop log uploader for this run
|
|
208
|
-
if log_uploader:
|
|
209
|
-
stop_log_uploader(log_queue, log_uploader)
|
|
210
|
-
log_uploader = None
|
|
211
|
-
|
|
212
210
|
# Stop the loop if `flwr-serverapp` is expected to process a single run
|
|
213
211
|
if run_once:
|
|
214
212
|
break
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def _parse_args_run_flwr_serverapp() -> argparse.ArgumentParser:
|
|
216
|
+
"""Parse flwr-serverapp command line arguments."""
|
|
217
|
+
parser = argparse.ArgumentParser(
|
|
218
|
+
description="Run a Flower ServerApp",
|
|
219
|
+
)
|
|
220
|
+
parser.add_argument(
|
|
221
|
+
"--serverappio-api-address",
|
|
222
|
+
default=SERVERAPPIO_API_DEFAULT_CLIENT_ADDRESS,
|
|
223
|
+
type=str,
|
|
224
|
+
help="Address of SuperLink's ServerAppIo API (IPv4, IPv6, or a domain name)."
|
|
225
|
+
f"By default, it is set to {SERVERAPPIO_API_DEFAULT_CLIENT_ADDRESS}.",
|
|
226
|
+
)
|
|
227
|
+
parser.add_argument(
|
|
228
|
+
"--run-once",
|
|
229
|
+
action="store_true",
|
|
230
|
+
help="When set, this process will start a single ServerApp for a pending Run. "
|
|
231
|
+
"If there is no pending Run, the process will exit.",
|
|
232
|
+
)
|
|
233
|
+
add_args_flwr_app_common(parser=parser)
|
|
234
|
+
return parser
|
|
@@ -19,7 +19,7 @@ from __future__ import annotations
|
|
|
19
19
|
|
|
20
20
|
import sys
|
|
21
21
|
from collections.abc import Awaitable
|
|
22
|
-
from typing import Callable, TypeVar
|
|
22
|
+
from typing import Callable, TypeVar, cast
|
|
23
23
|
|
|
24
24
|
from google.protobuf.message import Message as GrpcMessage
|
|
25
25
|
|
|
@@ -39,8 +39,9 @@ from flwr.proto.fleet_pb2 import ( # pylint: disable=E0611
|
|
|
39
39
|
)
|
|
40
40
|
from flwr.proto.run_pb2 import GetRunRequest, GetRunResponse # pylint: disable=E0611
|
|
41
41
|
from flwr.server.superlink.ffs.ffs import Ffs
|
|
42
|
+
from flwr.server.superlink.ffs.ffs_factory import FfsFactory
|
|
42
43
|
from flwr.server.superlink.fleet.message_handler import message_handler
|
|
43
|
-
from flwr.server.superlink.linkstate import LinkState
|
|
44
|
+
from flwr.server.superlink.linkstate import LinkState, LinkStateFactory
|
|
44
45
|
|
|
45
46
|
try:
|
|
46
47
|
from starlette.applications import Starlette
|
|
@@ -90,7 +91,7 @@ def rest_request_response(
|
|
|
90
91
|
async def create_node(request: CreateNodeRequest) -> CreateNodeResponse:
|
|
91
92
|
"""Create Node."""
|
|
92
93
|
# Get state from app
|
|
93
|
-
state: LinkState = app.state.STATE_FACTORY.state()
|
|
94
|
+
state: LinkState = cast(LinkStateFactory, app.state.STATE_FACTORY).state()
|
|
94
95
|
|
|
95
96
|
# Handle message
|
|
96
97
|
return message_handler.create_node(request=request, state=state)
|
|
@@ -100,7 +101,7 @@ async def create_node(request: CreateNodeRequest) -> CreateNodeResponse:
|
|
|
100
101
|
async def delete_node(request: DeleteNodeRequest) -> DeleteNodeResponse:
|
|
101
102
|
"""Delete Node Id."""
|
|
102
103
|
# Get state from app
|
|
103
|
-
state: LinkState = app.state.STATE_FACTORY.state()
|
|
104
|
+
state: LinkState = cast(LinkStateFactory, app.state.STATE_FACTORY).state()
|
|
104
105
|
|
|
105
106
|
# Handle message
|
|
106
107
|
return message_handler.delete_node(request=request, state=state)
|
|
@@ -110,7 +111,7 @@ async def delete_node(request: DeleteNodeRequest) -> DeleteNodeResponse:
|
|
|
110
111
|
async def pull_task_ins(request: PullTaskInsRequest) -> PullTaskInsResponse:
|
|
111
112
|
"""Pull TaskIns."""
|
|
112
113
|
# Get state from app
|
|
113
|
-
state: LinkState = app.state.STATE_FACTORY.state()
|
|
114
|
+
state: LinkState = cast(LinkStateFactory, app.state.STATE_FACTORY).state()
|
|
114
115
|
|
|
115
116
|
# Handle message
|
|
116
117
|
return message_handler.pull_task_ins(request=request, state=state)
|
|
@@ -121,7 +122,7 @@ async def pull_task_ins(request: PullTaskInsRequest) -> PullTaskInsResponse:
|
|
|
121
122
|
async def push_task_res(request: PushTaskResRequest) -> PushTaskResResponse:
|
|
122
123
|
"""Push TaskRes."""
|
|
123
124
|
# Get state from app
|
|
124
|
-
state: LinkState = app.state.STATE_FACTORY.state()
|
|
125
|
+
state: LinkState = cast(LinkStateFactory, app.state.STATE_FACTORY).state()
|
|
125
126
|
|
|
126
127
|
# Handle message
|
|
127
128
|
return message_handler.push_task_res(request=request, state=state)
|
|
@@ -131,7 +132,7 @@ async def push_task_res(request: PushTaskResRequest) -> PushTaskResResponse:
|
|
|
131
132
|
async def ping(request: PingRequest) -> PingResponse:
|
|
132
133
|
"""Ping."""
|
|
133
134
|
# Get state from app
|
|
134
|
-
state: LinkState = app.state.STATE_FACTORY.state()
|
|
135
|
+
state: LinkState = cast(LinkStateFactory, app.state.STATE_FACTORY).state()
|
|
135
136
|
|
|
136
137
|
# Handle message
|
|
137
138
|
return message_handler.ping(request=request, state=state)
|
|
@@ -141,7 +142,7 @@ async def ping(request: PingRequest) -> PingResponse:
|
|
|
141
142
|
async def get_run(request: GetRunRequest) -> GetRunResponse:
|
|
142
143
|
"""GetRun."""
|
|
143
144
|
# Get state from app
|
|
144
|
-
state: LinkState = app.state.STATE_FACTORY.state()
|
|
145
|
+
state: LinkState = cast(LinkStateFactory, app.state.STATE_FACTORY).state()
|
|
145
146
|
|
|
146
147
|
# Handle message
|
|
147
148
|
return message_handler.get_run(request=request, state=state)
|
|
@@ -151,7 +152,7 @@ async def get_run(request: GetRunRequest) -> GetRunResponse:
|
|
|
151
152
|
async def get_fab(request: GetFabRequest) -> GetFabResponse:
|
|
152
153
|
"""GetRun."""
|
|
153
154
|
# Get ffs from app
|
|
154
|
-
ffs: Ffs = app.state.FFS_FACTORY.
|
|
155
|
+
ffs: Ffs = cast(FfsFactory, app.state.FFS_FACTORY).ffs()
|
|
155
156
|
|
|
156
157
|
# Handle message
|
|
157
158
|
return message_handler.get_fab(request=request, ffs=ffs)
|