flwr-nightly 1.13.0.dev20241021__py3-none-any.whl → 1.13.0.dev20241111__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.

Files changed (92) hide show
  1. flwr/cli/build.py +2 -2
  2. flwr/cli/config_utils.py +97 -0
  3. flwr/cli/log.py +63 -97
  4. flwr/cli/new/templates/app/code/flwr_tune/dataset.py.tpl +1 -1
  5. flwr/cli/new/templates/app/pyproject.flowertune.toml.tpl +1 -0
  6. flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl +1 -1
  7. flwr/cli/run/run.py +34 -88
  8. flwr/client/app.py +23 -20
  9. flwr/client/clientapp/app.py +22 -18
  10. flwr/client/nodestate/__init__.py +25 -0
  11. flwr/client/nodestate/in_memory_nodestate.py +38 -0
  12. flwr/client/nodestate/nodestate.py +30 -0
  13. flwr/client/nodestate/nodestate_factory.py +37 -0
  14. flwr/client/{node_state.py → run_info_store.py} +4 -3
  15. flwr/client/supernode/app.py +6 -8
  16. flwr/common/args.py +83 -0
  17. flwr/common/config.py +10 -0
  18. flwr/common/constant.py +39 -5
  19. flwr/common/context.py +9 -4
  20. flwr/common/date.py +3 -3
  21. flwr/common/logger.py +108 -1
  22. flwr/common/object_ref.py +47 -16
  23. flwr/common/serde.py +24 -0
  24. flwr/common/telemetry.py +0 -6
  25. flwr/common/typing.py +10 -1
  26. flwr/proto/exec_pb2.py +14 -17
  27. flwr/proto/exec_pb2.pyi +14 -22
  28. flwr/proto/log_pb2.py +29 -0
  29. flwr/proto/log_pb2.pyi +39 -0
  30. flwr/proto/log_pb2_grpc.py +4 -0
  31. flwr/proto/log_pb2_grpc.pyi +4 -0
  32. flwr/proto/message_pb2.py +8 -8
  33. flwr/proto/message_pb2.pyi +4 -1
  34. flwr/proto/run_pb2.py +32 -27
  35. flwr/proto/run_pb2.pyi +26 -0
  36. flwr/proto/serverappio_pb2.py +52 -0
  37. flwr/proto/{driver_pb2.pyi → serverappio_pb2.pyi} +54 -0
  38. flwr/proto/serverappio_pb2_grpc.py +376 -0
  39. flwr/proto/serverappio_pb2_grpc.pyi +147 -0
  40. flwr/proto/simulationio_pb2.py +38 -0
  41. flwr/proto/simulationio_pb2.pyi +65 -0
  42. flwr/proto/simulationio_pb2_grpc.py +205 -0
  43. flwr/proto/simulationio_pb2_grpc.pyi +81 -0
  44. flwr/server/app.py +272 -105
  45. flwr/server/driver/driver.py +15 -1
  46. flwr/server/driver/grpc_driver.py +25 -36
  47. flwr/server/driver/inmemory_driver.py +6 -16
  48. flwr/server/run_serverapp.py +29 -23
  49. flwr/server/{superlink/state → serverapp}/__init__.py +3 -9
  50. flwr/server/serverapp/app.py +214 -0
  51. flwr/server/strategy/aggregate.py +4 -4
  52. flwr/server/strategy/fedadam.py +11 -1
  53. flwr/server/superlink/driver/__init__.py +1 -1
  54. flwr/server/superlink/driver/{driver_grpc.py → serverappio_grpc.py} +19 -16
  55. flwr/server/superlink/driver/{driver_servicer.py → serverappio_servicer.py} +125 -39
  56. flwr/server/superlink/fleet/grpc_adapter/grpc_adapter_servicer.py +4 -2
  57. flwr/server/superlink/fleet/grpc_bidi/grpc_server.py +2 -2
  58. flwr/server/superlink/fleet/grpc_rere/fleet_servicer.py +4 -2
  59. flwr/server/superlink/fleet/grpc_rere/server_interceptor.py +2 -2
  60. flwr/server/superlink/fleet/message_handler/message_handler.py +7 -7
  61. flwr/server/superlink/fleet/rest_rere/rest_api.py +7 -7
  62. flwr/server/superlink/fleet/vce/vce_api.py +23 -23
  63. flwr/server/superlink/linkstate/__init__.py +28 -0
  64. flwr/server/superlink/{state/in_memory_state.py → linkstate/in_memory_linkstate.py} +184 -36
  65. flwr/server/superlink/{state/state.py → linkstate/linkstate.py} +149 -19
  66. flwr/server/superlink/{state/state_factory.py → linkstate/linkstate_factory.py} +9 -9
  67. flwr/server/superlink/{state/sqlite_state.py → linkstate/sqlite_linkstate.py} +306 -65
  68. flwr/server/superlink/{state → linkstate}/utils.py +81 -30
  69. flwr/server/superlink/simulation/__init__.py +15 -0
  70. flwr/server/superlink/simulation/simulationio_grpc.py +65 -0
  71. flwr/server/superlink/simulation/simulationio_servicer.py +153 -0
  72. flwr/simulation/__init__.py +5 -1
  73. flwr/simulation/app.py +273 -345
  74. flwr/simulation/legacy_app.py +382 -0
  75. flwr/simulation/ray_transport/ray_client_proxy.py +2 -2
  76. flwr/simulation/run_simulation.py +57 -131
  77. flwr/simulation/simulationio_connection.py +86 -0
  78. flwr/superexec/app.py +6 -134
  79. flwr/superexec/deployment.py +61 -66
  80. flwr/superexec/exec_grpc.py +15 -8
  81. flwr/superexec/exec_servicer.py +36 -65
  82. flwr/superexec/executor.py +26 -7
  83. flwr/superexec/simulation.py +54 -107
  84. {flwr_nightly-1.13.0.dev20241021.dist-info → flwr_nightly-1.13.0.dev20241111.dist-info}/METADATA +5 -4
  85. {flwr_nightly-1.13.0.dev20241021.dist-info → flwr_nightly-1.13.0.dev20241111.dist-info}/RECORD +88 -69
  86. {flwr_nightly-1.13.0.dev20241021.dist-info → flwr_nightly-1.13.0.dev20241111.dist-info}/entry_points.txt +2 -0
  87. flwr/client/node_state_tests.py +0 -66
  88. flwr/proto/driver_pb2.py +0 -42
  89. flwr/proto/driver_pb2_grpc.py +0 -239
  90. flwr/proto/driver_pb2_grpc.pyi +0 -94
  91. {flwr_nightly-1.13.0.dev20241021.dist-info → flwr_nightly-1.13.0.dev20241111.dist-info}/LICENSE +0 -0
  92. {flwr_nightly-1.13.0.dev20241021.dist-info → flwr_nightly-1.13.0.dev20241111.dist-info}/WHEEL +0 -0
@@ -21,7 +21,6 @@ import logging
21
21
  import sys
22
22
  import threading
23
23
  import traceback
24
- from argparse import Namespace
25
24
  from logging import DEBUG, ERROR, INFO, WARNING
26
25
  from pathlib import Path
27
26
  from time import sleep
@@ -29,69 +28,28 @@ from typing import Any, Optional
29
28
 
30
29
  from flwr.cli.config_utils import load_and_validate
31
30
  from flwr.client import ClientApp
32
- from flwr.common import EventType, event, log
31
+ from flwr.common import Context, EventType, RecordSet, event, log, now
33
32
  from flwr.common.config import get_fused_config_from_dir, parse_config_args
34
- from flwr.common.constant import RUN_ID_NUM_BYTES
33
+ from flwr.common.constant import RUN_ID_NUM_BYTES, Status
35
34
  from flwr.common.logger import (
36
35
  set_logger_propagation,
37
36
  update_console_handler,
38
- warn_deprecated_feature,
39
37
  warn_deprecated_feature_with_example,
40
38
  )
41
- from flwr.common.typing import Run, UserConfig
39
+ from flwr.common.typing import Run, RunStatus, UserConfig
42
40
  from flwr.server.driver import Driver, InMemoryDriver
43
- from flwr.server.run_serverapp import run as run_server_app
41
+ from flwr.server.run_serverapp import run as _run
44
42
  from flwr.server.server_app import ServerApp
45
43
  from flwr.server.superlink.fleet import vce
46
44
  from flwr.server.superlink.fleet.vce.backend.backend import BackendConfig
47
- from flwr.server.superlink.state import StateFactory
48
- from flwr.server.superlink.state.utils import generate_rand_int_from_bytes
45
+ from flwr.server.superlink.linkstate import LinkStateFactory
46
+ from flwr.server.superlink.linkstate.in_memory_linkstate import RunRecord
47
+ from flwr.server.superlink.linkstate.utils import generate_rand_int_from_bytes
49
48
  from flwr.simulation.ray_transport.utils import (
50
49
  enable_tf_gpu_growth as enable_gpu_growth,
51
50
  )
52
51
 
53
52
 
54
- def _check_args_do_not_interfere(args: Namespace) -> bool:
55
- """Ensure decoupling of flags for different ways to start the simulation."""
56
- mode_one_args = ["app", "run_config"]
57
- mode_two_args = ["client_app", "server_app"]
58
-
59
- def _resolve_message(conflict_keys: list[str]) -> str:
60
- return ",".join([f"`--{key}`".replace("_", "-") for key in conflict_keys])
61
-
62
- # When passing `--app`, `--app-dir` is ignored
63
- if args.app and args.app_dir:
64
- log(ERROR, "Either `--app` or `--app-dir` can be set, but not both.")
65
- return False
66
-
67
- if any(getattr(args, key) for key in mode_one_args):
68
- if any(getattr(args, key) for key in mode_two_args):
69
- log(
70
- ERROR,
71
- "Passing any of {%s} alongside with any of {%s}",
72
- _resolve_message(mode_one_args),
73
- _resolve_message(mode_two_args),
74
- )
75
- return False
76
-
77
- if not args.app:
78
- log(ERROR, "You need to pass --app")
79
- return False
80
-
81
- return True
82
-
83
- # Ensure all args are set (required for the non-FAB mode of execution)
84
- if not all(getattr(args, key) for key in mode_two_args):
85
- log(
86
- ERROR,
87
- "Passing all of %s keys are required.",
88
- _resolve_message(mode_two_args),
89
- )
90
- return False
91
-
92
- return True
93
-
94
-
95
53
  def _replace_keys(d: Any, match: str, target: str) -> Any:
96
54
  if isinstance(d, dict):
97
55
  return {
@@ -114,19 +72,6 @@ def run_simulation_from_cli() -> None:
114
72
  event_details={"backend": args.backend, "num-supernodes": args.num_supernodes},
115
73
  )
116
74
 
117
- # Add warnings for deprecated server_app and client_app arguments
118
- if args.server_app:
119
- warn_deprecated_feature(
120
- "The `--server-app` argument is deprecated. "
121
- "Please use the `--app` argument instead."
122
- )
123
-
124
- if args.client_app:
125
- warn_deprecated_feature(
126
- "The `--client-app` argument is deprecated. "
127
- "Use the `--app` argument instead."
128
- )
129
-
130
75
  if args.enable_tf_gpu_growth:
131
76
  warn_deprecated_feature_with_example(
132
77
  "Passing `--enable-tf-gpu-growth` is deprecated.",
@@ -143,60 +88,39 @@ def run_simulation_from_cli() -> None:
143
88
  backend_config_dict = _replace_keys(backend_config_dict, match="-", target="_")
144
89
  log(DEBUG, "backend_config_dict: %s", backend_config_dict)
145
90
 
146
- # We are supporting two modes for the CLI entrypoint:
147
- # 1) Running an app dir containing a `pyproject.toml`
148
- # 2) Running any ClientApp and SeverApp w/o pyproject.toml being present
149
- # For 2), some CLI args are compulsory, but they are not required for 1)
150
- # We first do these checks
151
- args_check_pass = _check_args_do_not_interfere(args)
152
- if not args_check_pass:
153
- sys.exit("Simulation Engine cannot start.")
154
-
155
91
  run_id = (
156
92
  generate_rand_int_from_bytes(RUN_ID_NUM_BYTES)
157
93
  if args.run_id is None
158
94
  else args.run_id
159
95
  )
160
- if args.app:
161
- # Mode 1
162
- app_path = Path(args.app)
163
- if not app_path.is_dir():
164
- log(ERROR, "--app is not a directory")
165
- sys.exit("Simulation Engine cannot start.")
166
-
167
- # Load pyproject.toml
168
- config, errors, warnings = load_and_validate(
169
- app_path / "pyproject.toml", check_module=False
170
- )
171
- if errors:
172
- raise ValueError(errors)
173
96
 
174
- if warnings:
175
- log(WARNING, warnings)
97
+ app_path = Path(args.app)
98
+ if not app_path.is_dir():
99
+ log(ERROR, "--app is not a directory")
100
+ sys.exit("Simulation Engine cannot start.")
176
101
 
177
- if config is None:
178
- raise ValueError("Config extracted from FAB's pyproject.toml is not valid")
102
+ # Load pyproject.toml
103
+ config, errors, warnings = load_and_validate(
104
+ app_path / "pyproject.toml", check_module=False
105
+ )
106
+ if errors:
107
+ raise ValueError(errors)
179
108
 
180
- # Get ClientApp and SeverApp components
181
- app_components = config["tool"]["flwr"]["app"]["components"]
182
- client_app_attr = app_components["clientapp"]
183
- server_app_attr = app_components["serverapp"]
109
+ if warnings:
110
+ log(WARNING, warnings)
184
111
 
185
- override_config = parse_config_args(
186
- [args.run_config] if args.run_config else args.run_config
187
- )
188
- fused_config = get_fused_config_from_dir(app_path, override_config)
189
- app_dir = args.app
190
- is_app = True
112
+ if config is None:
113
+ raise ValueError("Config extracted from FAB's pyproject.toml is not valid")
191
114
 
192
- else:
193
- # Mode 2
194
- client_app_attr = args.client_app
195
- server_app_attr = args.server_app
196
- override_config = {}
197
- fused_config = None
198
- app_dir = args.app_dir
199
- is_app = False
115
+ # Get ClientApp and SeverApp components
116
+ app_components = config["tool"]["flwr"]["app"]["components"]
117
+ client_app_attr = app_components["clientapp"]
118
+ server_app_attr = app_components["serverapp"]
119
+
120
+ override_config = parse_config_args(
121
+ [args.run_config] if args.run_config else args.run_config
122
+ )
123
+ fused_config = get_fused_config_from_dir(app_path, override_config)
200
124
 
201
125
  # Create run
202
126
  run = Run(
@@ -213,13 +137,13 @@ def run_simulation_from_cli() -> None:
213
137
  num_supernodes=args.num_supernodes,
214
138
  backend_name=args.backend,
215
139
  backend_config=backend_config_dict,
216
- app_dir=app_dir,
140
+ app_dir=args.app,
217
141
  run=run,
218
142
  enable_tf_gpu_growth=args.enable_tf_gpu_growth,
219
143
  delay_start=args.delay_start,
220
144
  verbose_logging=args.verbose,
221
145
  server_app_run_config=fused_config,
222
- is_app=is_app,
146
+ is_app=True,
223
147
  exit_event=EventType.CLI_FLOWER_SIMULATION_LEAVE,
224
148
  )
225
149
 
@@ -310,6 +234,7 @@ def run_serverapp_th(
310
234
  f_stop: threading.Event,
311
235
  has_exception: threading.Event,
312
236
  enable_tf_gpu_growth: bool,
237
+ run_id: int,
313
238
  ) -> threading.Thread:
314
239
  """Run SeverApp in a thread."""
315
240
 
@@ -332,11 +257,20 @@ def run_serverapp_th(
332
257
  log(INFO, "Enabling GPU growth for Tensorflow on the server thread.")
333
258
  enable_gpu_growth()
334
259
 
260
+ # Initialize Context
261
+ context = Context(
262
+ run_id=run_id,
263
+ node_id=0,
264
+ node_config={},
265
+ state=RecordSet(),
266
+ run_config=_server_app_run_config,
267
+ )
268
+
335
269
  # Run ServerApp
336
- run_server_app(
270
+ _run(
337
271
  driver=_driver,
272
+ context=context,
338
273
  server_app_dir=_server_app_dir,
339
- server_app_run_config=_server_app_run_config,
340
274
  server_app_attr=_server_app_attr,
341
275
  loaded_server_app=_server_app,
342
276
  )
@@ -389,7 +323,7 @@ def _main_loop(
389
323
  ) -> None:
390
324
  """Start ServerApp on a separate thread, then launch Simulation Engine."""
391
325
  # Initialize StateFactory
392
- state_factory = StateFactory(":flwr-in-memory-state:")
326
+ state_factory = LinkStateFactory(":flwr-in-memory-state:")
393
327
 
394
328
  f_stop = threading.Event()
395
329
  # A Threading event to indicate if an exception was raised in the ServerApp thread
@@ -399,13 +333,21 @@ def _main_loop(
399
333
  try:
400
334
  # Register run
401
335
  log(DEBUG, "Pre-registering run with id %s", run.run_id)
402
- state_factory.state().run_ids[run.run_id] = run # type: ignore
336
+ init_status = RunStatus(Status.RUNNING, "", "")
337
+ state_factory.state().run_ids[run.run_id] = RunRecord( # type: ignore
338
+ run=run,
339
+ status=init_status,
340
+ starting_at=now().isoformat(),
341
+ running_at=now().isoformat(),
342
+ finished_at="",
343
+ )
403
344
 
404
345
  if server_app_run_config is None:
405
346
  server_app_run_config = {}
406
347
 
407
348
  # Initialize Driver
408
- driver = InMemoryDriver(run_id=run.run_id, state_factory=state_factory)
349
+ driver = InMemoryDriver(state_factory=state_factory)
350
+ driver.set_run(run_id=run.run_id)
409
351
 
410
352
  # Get and run ServerApp thread
411
353
  serverapp_th = run_serverapp_th(
@@ -417,6 +359,7 @@ def _main_loop(
417
359
  f_stop=f_stop,
418
360
  has_exception=server_app_thread_has_exception,
419
361
  enable_tf_gpu_growth=enable_tf_gpu_growth,
362
+ run_id=run.run_id,
420
363
  )
421
364
 
422
365
  # Buffer time so the `ServerApp` in separate thread is ready
@@ -566,20 +509,10 @@ def _parse_args_run_simulation() -> argparse.ArgumentParser:
566
509
  parser.add_argument(
567
510
  "--app",
568
511
  type=str,
569
- default=None,
512
+ required=True,
570
513
  help="Path to a directory containing a FAB-like structure with a "
571
514
  "pyproject.toml.",
572
515
  )
573
- parser.add_argument(
574
- "--server-app",
575
- help="(DEPRECATED: use --app instead) For example: `server:app` or "
576
- "`project.package.module:wrapper.app`",
577
- )
578
- parser.add_argument(
579
- "--client-app",
580
- help="(DEPRECATED: use --app instead) For example: `client:app` or "
581
- "`project.package.module:wrapper.app`",
582
- )
583
516
  parser.add_argument(
584
517
  "--num-supernodes",
585
518
  type=int,
@@ -628,13 +561,6 @@ def _parse_args_run_simulation() -> argparse.ArgumentParser:
628
561
  help="When unset, only INFO, WARNING and ERROR log messages will be shown. "
629
562
  "If set, DEBUG-level logs will be displayed. ",
630
563
  )
631
- parser.add_argument(
632
- "--app-dir",
633
- default="",
634
- help="Add specified directory to the PYTHONPATH and load"
635
- "ClientApp and ServerApp from there."
636
- " Default: current working directory.",
637
- )
638
564
  parser.add_argument(
639
565
  "--flwr-dir",
640
566
  default=None,
@@ -0,0 +1,86 @@
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
+ """Flower SimulationIo connection."""
16
+
17
+
18
+ from logging import DEBUG, WARNING
19
+ from typing import Optional, cast
20
+
21
+ import grpc
22
+
23
+ from flwr.common.constant import SIMULATIONIO_API_DEFAULT_ADDRESS
24
+ from flwr.common.grpc import create_channel
25
+ from flwr.common.logger import log
26
+ from flwr.proto.simulationio_pb2_grpc import SimulationIoStub # pylint: disable=E0611
27
+
28
+
29
+ class SimulationIoConnection:
30
+ """`SimulationIoConnection` provides an interface to the SimulationIo API.
31
+
32
+ Parameters
33
+ ----------
34
+ simulationio_service_address : str (default: "[::]:9094")
35
+ The address (URL, IPv6, IPv4) of the SuperLink SimulationIo API service.
36
+ root_certificates : Optional[bytes] (default: None)
37
+ The PEM-encoded root certificates as a byte string.
38
+ If provided, a secure connection using the certificates will be
39
+ established to an SSL-enabled Flower server.
40
+ """
41
+
42
+ def __init__( # pylint: disable=too-many-arguments
43
+ self,
44
+ simulationio_service_address: str = SIMULATIONIO_API_DEFAULT_ADDRESS,
45
+ root_certificates: Optional[bytes] = None,
46
+ ) -> None:
47
+ self._addr = simulationio_service_address
48
+ self._cert = root_certificates
49
+ self._grpc_stub: Optional[SimulationIoStub] = None
50
+ self._channel: Optional[grpc.Channel] = None
51
+
52
+ @property
53
+ def _is_connected(self) -> bool:
54
+ """Check if connected to the SimulationIo API server."""
55
+ return self._channel is not None
56
+
57
+ @property
58
+ def _stub(self) -> SimulationIoStub:
59
+ """SimulationIo stub."""
60
+ if not self._is_connected:
61
+ self._connect()
62
+ return cast(SimulationIoStub, self._grpc_stub)
63
+
64
+ def _connect(self) -> None:
65
+ """Connect to the SimulationIo API."""
66
+ if self._is_connected:
67
+ log(WARNING, "Already connected")
68
+ return
69
+ self._channel = create_channel(
70
+ server_address=self._addr,
71
+ insecure=(self._cert is None),
72
+ root_certificates=self._cert,
73
+ )
74
+ self._grpc_stub = SimulationIoStub(self._channel)
75
+ log(DEBUG, "[SimulationIO] Connected to %s", self._addr)
76
+
77
+ def _disconnect(self) -> None:
78
+ """Disconnect from the SimulationIo API."""
79
+ if not self._is_connected:
80
+ log(DEBUG, "Already disconnected")
81
+ return
82
+ channel: grpc.Channel = self._channel
83
+ self._channel = None
84
+ self._grpc_stub = None
85
+ channel.close()
86
+ log(DEBUG, "[SimulationIO] Disconnected")
flwr/superexec/app.py CHANGED
@@ -16,20 +16,11 @@
16
16
 
17
17
  import argparse
18
18
  import sys
19
- from logging import INFO, WARN
20
- from pathlib import Path
21
- from typing import Optional
19
+ from logging import INFO
22
20
 
23
- import grpc
24
-
25
- from flwr.common import EventType, event, log
26
- from flwr.common.address import parse_address
27
- from flwr.common.config import parse_config_args
28
- from flwr.common.constant import EXEC_API_DEFAULT_ADDRESS
29
- from flwr.common.exit_handlers import register_exit_handlers
21
+ from flwr.common import log
30
22
  from flwr.common.object_ref import load_app, validate
31
23
 
32
- from .exec_grpc import run_superexec_api_grpc
33
24
  from .executor import Executor
34
25
 
35
26
 
@@ -37,133 +28,14 @@ def run_superexec() -> None:
37
28
  """Run Flower SuperExec."""
38
29
  log(INFO, "Starting Flower SuperExec")
39
30
 
40
- event(EventType.RUN_SUPEREXEC_ENTER)
41
-
42
- args = _parse_args_run_superexec().parse_args()
43
-
44
- # Parse IP address
45
- parsed_address = parse_address(args.address)
46
- if not parsed_address:
47
- sys.exit(f"SuperExec IP address ({args.address}) cannot be parsed.")
48
- host, port, is_v6 = parsed_address
49
- address = f"[{host}]:{port}" if is_v6 else f"{host}:{port}"
50
-
51
- # Obtain certificates
52
- certificates = _try_obtain_certificates(args)
53
-
54
- # Start SuperExec API
55
- superexec_server: grpc.Server = run_superexec_api_grpc(
56
- address=address,
57
- executor=_load_executor(args),
58
- certificates=certificates,
59
- config=parse_config_args(
60
- [args.executor_config] if args.executor_config else args.executor_config
61
- ),
62
- )
63
-
64
- grpc_servers = [superexec_server]
65
-
66
- # Graceful shutdown
67
- register_exit_handlers(
68
- event_type=EventType.RUN_SUPEREXEC_LEAVE,
69
- grpc_servers=grpc_servers,
70
- bckg_threads=None,
71
- )
72
-
73
- superexec_server.wait_for_termination()
74
-
75
-
76
- def _parse_args_run_superexec() -> argparse.ArgumentParser:
77
- """Parse command line arguments for SuperExec."""
78
- parser = argparse.ArgumentParser(
79
- description="Start a Flower SuperExec",
80
- )
81
- parser.add_argument(
82
- "--address",
83
- help="SuperExec (gRPC) server address (IPv4, IPv6, or a domain name)",
84
- default=EXEC_API_DEFAULT_ADDRESS,
85
- )
86
- parser.add_argument(
87
- "--executor",
88
- help="For example: `deployment:exec` or `project.package.module:wrapper.exec`.",
89
- default="flwr.superexec.deployment:executor",
90
- )
91
- parser.add_argument(
92
- "--executor-dir",
93
- help="The directory for the executor.",
94
- default=".",
95
- )
96
- parser.add_argument(
97
- "--executor-config",
98
- help="Key-value pairs for the executor config, separated by spaces. "
99
- 'For example:\n\n`--executor-config \'superlink="superlink:9091" '
100
- 'root-certificates="certificates/superlink-ca.crt"\'`',
101
- )
102
- parser.add_argument(
103
- "--insecure",
104
- action="store_true",
105
- help="Run the SuperExec without HTTPS, regardless of whether certificate "
106
- "paths are provided. By default, the server runs with HTTPS enabled. "
107
- "Use this flag only if you understand the risks.",
108
- )
109
- parser.add_argument(
110
- "--ssl-certfile",
111
- help="SuperExec server SSL certificate file (as a path str) "
112
- "to create a secure connection.",
113
- type=str,
114
- default=None,
115
- )
116
- parser.add_argument(
117
- "--ssl-keyfile",
118
- help="SuperExec server SSL private key file (as a path str) "
119
- "to create a secure connection.",
120
- type=str,
121
- )
122
- parser.add_argument(
123
- "--ssl-ca-certfile",
124
- help="SuperExec server SSL CA certificate file (as a path str) "
125
- "to create a secure connection.",
126
- type=str,
127
- )
128
- return parser
129
-
130
-
131
- def _try_obtain_certificates(
132
- args: argparse.Namespace,
133
- ) -> Optional[tuple[bytes, bytes, bytes]]:
134
- # Obtain certificates
135
- if args.insecure:
136
- log(WARN, "Option `--insecure` was set. Starting insecure HTTP server.")
137
- return None
138
- # Check if certificates are provided
139
- if args.ssl_certfile and args.ssl_keyfile and args.ssl_ca_certfile:
140
- if not Path(args.ssl_ca_certfile).is_file():
141
- sys.exit("Path argument `--ssl-ca-certfile` does not point to a file.")
142
- if not Path(args.ssl_certfile).is_file():
143
- sys.exit("Path argument `--ssl-certfile` does not point to a file.")
144
- if not Path(args.ssl_keyfile).is_file():
145
- sys.exit("Path argument `--ssl-keyfile` does not point to a file.")
146
- certificates = (
147
- Path(args.ssl_ca_certfile).read_bytes(), # CA certificate
148
- Path(args.ssl_certfile).read_bytes(), # server certificate
149
- Path(args.ssl_keyfile).read_bytes(), # server private key
150
- )
151
- return certificates
152
- if args.ssl_certfile or args.ssl_keyfile or args.ssl_ca_certfile:
153
- sys.exit(
154
- "You need to provide valid file paths to `--ssl-certfile`, "
155
- "`--ssl-keyfile`, and `—-ssl-ca-certfile` to create a secure "
156
- "connection in SuperExec server (gRPC-rere)."
157
- )
158
31
  sys.exit(
159
- "Certificates are required unless running in insecure mode. "
160
- "Please provide certificate paths to `--ssl-certfile`, "
161
- "`--ssl-keyfile`, and `—-ssl-ca-certfile` or run the server "
162
- "in insecure mode using '--insecure' if you understand the risks."
32
+ "Manually launching the SuperExec is deprecated. Since `flwr 1.13.0` "
33
+ "the executor service runs in the SuperLink. Launching it manually is not "
34
+ "recommended."
163
35
  )
164
36
 
165
37
 
166
- def _load_executor(
38
+ def load_executor(
167
39
  args: argparse.Namespace,
168
40
  ) -> Executor:
169
41
  """Get the executor plugin."""