flwr-nightly 1.8.0.dev20240315__py3-none-any.whl → 1.11.0.dev20240813__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 (237) hide show
  1. flwr/cli/app.py +7 -0
  2. flwr/cli/build.py +150 -0
  3. flwr/cli/config_utils.py +219 -0
  4. flwr/cli/example.py +3 -1
  5. flwr/cli/install.py +227 -0
  6. flwr/cli/new/new.py +179 -48
  7. flwr/cli/new/templates/app/.gitignore.tpl +160 -0
  8. flwr/cli/new/templates/app/README.flowertune.md.tpl +56 -0
  9. flwr/cli/new/templates/app/README.md.tpl +1 -5
  10. flwr/cli/new/templates/app/code/__init__.py.tpl +1 -1
  11. flwr/cli/new/templates/app/code/client.huggingface.py.tpl +65 -0
  12. flwr/cli/new/templates/app/code/client.jax.py.tpl +56 -0
  13. flwr/cli/new/templates/app/code/client.mlx.py.tpl +93 -0
  14. flwr/cli/new/templates/app/code/client.numpy.py.tpl +3 -2
  15. flwr/cli/new/templates/app/code/client.pytorch.py.tpl +23 -11
  16. flwr/cli/new/templates/app/code/client.sklearn.py.tpl +97 -0
  17. flwr/cli/new/templates/app/code/client.tensorflow.py.tpl +60 -1
  18. flwr/cli/new/templates/app/code/flwr_tune/__init__.py +15 -0
  19. flwr/cli/new/templates/app/code/flwr_tune/app.py.tpl +89 -0
  20. flwr/cli/new/templates/app/code/flwr_tune/client.py.tpl +126 -0
  21. flwr/cli/new/templates/app/code/flwr_tune/config.yaml.tpl +34 -0
  22. flwr/cli/new/templates/app/code/flwr_tune/dataset.py.tpl +57 -0
  23. flwr/cli/new/templates/app/code/flwr_tune/models.py.tpl +59 -0
  24. flwr/cli/new/templates/app/code/flwr_tune/server.py.tpl +48 -0
  25. flwr/cli/new/templates/app/code/flwr_tune/static_config.yaml.tpl +11 -0
  26. flwr/cli/new/templates/app/code/server.huggingface.py.tpl +23 -0
  27. flwr/cli/new/templates/app/code/server.jax.py.tpl +20 -0
  28. flwr/cli/new/templates/app/code/server.mlx.py.tpl +20 -0
  29. flwr/cli/new/templates/app/code/server.numpy.py.tpl +17 -9
  30. flwr/cli/new/templates/app/code/server.pytorch.py.tpl +21 -18
  31. flwr/cli/new/templates/app/code/server.sklearn.py.tpl +24 -0
  32. flwr/cli/new/templates/app/code/server.tensorflow.py.tpl +29 -1
  33. flwr/cli/new/templates/app/code/task.huggingface.py.tpl +99 -0
  34. flwr/cli/new/templates/app/code/task.jax.py.tpl +57 -0
  35. flwr/cli/new/templates/app/code/task.mlx.py.tpl +102 -0
  36. flwr/cli/new/templates/app/code/task.pytorch.py.tpl +28 -23
  37. flwr/cli/new/templates/app/code/task.tensorflow.py.tpl +53 -0
  38. flwr/cli/new/templates/app/pyproject.flowertune.toml.tpl +39 -0
  39. flwr/cli/new/templates/app/pyproject.huggingface.toml.tpl +38 -0
  40. flwr/cli/new/templates/app/pyproject.jax.toml.tpl +34 -0
  41. flwr/cli/new/templates/app/pyproject.mlx.toml.tpl +39 -0
  42. flwr/cli/new/templates/app/pyproject.numpy.toml.tpl +25 -12
  43. flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl +29 -14
  44. flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl +33 -0
  45. flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl +29 -14
  46. flwr/cli/run/run.py +168 -17
  47. flwr/cli/utils.py +75 -4
  48. flwr/client/__init__.py +6 -1
  49. flwr/client/app.py +239 -248
  50. flwr/client/client_app.py +70 -9
  51. flwr/client/dpfedavg_numpy_client.py +1 -1
  52. flwr/client/grpc_adapter_client/__init__.py +15 -0
  53. flwr/client/grpc_adapter_client/connection.py +97 -0
  54. flwr/client/grpc_client/connection.py +18 -5
  55. flwr/client/grpc_rere_client/__init__.py +1 -1
  56. flwr/client/grpc_rere_client/client_interceptor.py +158 -0
  57. flwr/client/grpc_rere_client/connection.py +127 -33
  58. flwr/client/grpc_rere_client/grpc_adapter.py +140 -0
  59. flwr/client/heartbeat.py +74 -0
  60. flwr/client/message_handler/__init__.py +1 -1
  61. flwr/client/message_handler/message_handler.py +7 -7
  62. flwr/client/mod/__init__.py +5 -5
  63. flwr/client/mod/centraldp_mods.py +4 -2
  64. flwr/client/mod/comms_mods.py +4 -4
  65. flwr/client/mod/localdp_mod.py +9 -4
  66. flwr/client/mod/secure_aggregation/__init__.py +1 -1
  67. flwr/client/mod/secure_aggregation/secaggplus_mod.py +1 -1
  68. flwr/client/mod/utils.py +1 -1
  69. flwr/client/node_state.py +60 -10
  70. flwr/client/node_state_tests.py +4 -3
  71. flwr/client/rest_client/__init__.py +1 -1
  72. flwr/client/rest_client/connection.py +177 -157
  73. flwr/client/supernode/__init__.py +26 -0
  74. flwr/client/supernode/app.py +464 -0
  75. flwr/client/typing.py +1 -0
  76. flwr/common/__init__.py +13 -11
  77. flwr/common/address.py +1 -1
  78. flwr/common/config.py +193 -0
  79. flwr/common/constant.py +42 -1
  80. flwr/common/context.py +26 -1
  81. flwr/common/date.py +1 -1
  82. flwr/common/dp.py +1 -1
  83. flwr/common/grpc.py +6 -2
  84. flwr/common/logger.py +79 -8
  85. flwr/common/message.py +167 -105
  86. flwr/common/object_ref.py +126 -25
  87. flwr/common/record/__init__.py +1 -1
  88. flwr/common/record/parametersrecord.py +0 -1
  89. flwr/common/record/recordset.py +78 -27
  90. flwr/common/recordset_compat.py +8 -1
  91. flwr/common/retry_invoker.py +25 -13
  92. flwr/common/secure_aggregation/__init__.py +1 -1
  93. flwr/common/secure_aggregation/crypto/__init__.py +1 -1
  94. flwr/common/secure_aggregation/crypto/shamir.py +1 -1
  95. flwr/common/secure_aggregation/crypto/symmetric_encryption.py +21 -2
  96. flwr/common/secure_aggregation/ndarrays_arithmetic.py +1 -1
  97. flwr/common/secure_aggregation/quantization.py +1 -1
  98. flwr/common/secure_aggregation/secaggplus_constants.py +1 -1
  99. flwr/common/secure_aggregation/secaggplus_utils.py +1 -1
  100. flwr/common/serde.py +209 -3
  101. flwr/common/telemetry.py +25 -0
  102. flwr/common/typing.py +38 -0
  103. flwr/common/version.py +14 -0
  104. flwr/proto/clientappio_pb2.py +41 -0
  105. flwr/proto/clientappio_pb2.pyi +110 -0
  106. flwr/proto/clientappio_pb2_grpc.py +101 -0
  107. flwr/proto/clientappio_pb2_grpc.pyi +40 -0
  108. flwr/proto/common_pb2.py +36 -0
  109. flwr/proto/common_pb2.pyi +121 -0
  110. flwr/proto/common_pb2_grpc.py +4 -0
  111. flwr/proto/common_pb2_grpc.pyi +4 -0
  112. flwr/proto/driver_pb2.py +26 -19
  113. flwr/proto/driver_pb2.pyi +34 -0
  114. flwr/proto/driver_pb2_grpc.py +70 -0
  115. flwr/proto/driver_pb2_grpc.pyi +28 -0
  116. flwr/proto/exec_pb2.py +43 -0
  117. flwr/proto/exec_pb2.pyi +95 -0
  118. flwr/proto/exec_pb2_grpc.py +101 -0
  119. flwr/proto/exec_pb2_grpc.pyi +41 -0
  120. flwr/proto/fab_pb2.py +30 -0
  121. flwr/proto/fab_pb2.pyi +56 -0
  122. flwr/proto/fab_pb2_grpc.py +4 -0
  123. flwr/proto/fab_pb2_grpc.pyi +4 -0
  124. flwr/proto/fleet_pb2.py +29 -23
  125. flwr/proto/fleet_pb2.pyi +33 -0
  126. flwr/proto/fleet_pb2_grpc.py +102 -0
  127. flwr/proto/fleet_pb2_grpc.pyi +35 -0
  128. flwr/proto/grpcadapter_pb2.py +32 -0
  129. flwr/proto/grpcadapter_pb2.pyi +43 -0
  130. flwr/proto/grpcadapter_pb2_grpc.py +66 -0
  131. flwr/proto/grpcadapter_pb2_grpc.pyi +24 -0
  132. flwr/proto/message_pb2.py +41 -0
  133. flwr/proto/message_pb2.pyi +122 -0
  134. flwr/proto/message_pb2_grpc.py +4 -0
  135. flwr/proto/message_pb2_grpc.pyi +4 -0
  136. flwr/proto/run_pb2.py +35 -0
  137. flwr/proto/run_pb2.pyi +76 -0
  138. flwr/proto/run_pb2_grpc.py +4 -0
  139. flwr/proto/run_pb2_grpc.pyi +4 -0
  140. flwr/proto/task_pb2.py +7 -8
  141. flwr/proto/task_pb2.pyi +8 -5
  142. flwr/server/__init__.py +4 -8
  143. flwr/server/app.py +298 -350
  144. flwr/server/compat/app.py +6 -57
  145. flwr/server/compat/app_utils.py +5 -4
  146. flwr/server/compat/driver_client_proxy.py +29 -48
  147. flwr/server/compat/legacy_context.py +5 -4
  148. flwr/server/driver/__init__.py +2 -0
  149. flwr/server/driver/driver.py +22 -132
  150. flwr/server/driver/grpc_driver.py +224 -74
  151. flwr/server/driver/inmemory_driver.py +183 -0
  152. flwr/server/history.py +20 -20
  153. flwr/server/run_serverapp.py +121 -34
  154. flwr/server/server.py +11 -7
  155. flwr/server/server_app.py +59 -10
  156. flwr/server/serverapp_components.py +52 -0
  157. flwr/server/strategy/__init__.py +2 -2
  158. flwr/server/strategy/bulyan.py +1 -1
  159. flwr/server/strategy/dp_adaptive_clipping.py +3 -3
  160. flwr/server/strategy/dp_fixed_clipping.py +4 -3
  161. flwr/server/strategy/dpfedavg_adaptive.py +1 -1
  162. flwr/server/strategy/dpfedavg_fixed.py +1 -1
  163. flwr/server/strategy/fedadagrad.py +1 -1
  164. flwr/server/strategy/fedadam.py +1 -1
  165. flwr/server/strategy/fedavg_android.py +1 -1
  166. flwr/server/strategy/fedavgm.py +1 -1
  167. flwr/server/strategy/fedmedian.py +1 -1
  168. flwr/server/strategy/fedopt.py +1 -1
  169. flwr/server/strategy/fedprox.py +1 -1
  170. flwr/server/strategy/fedxgb_bagging.py +1 -1
  171. flwr/server/strategy/fedxgb_cyclic.py +1 -1
  172. flwr/server/strategy/fedxgb_nn_avg.py +1 -1
  173. flwr/server/strategy/fedyogi.py +1 -1
  174. flwr/server/strategy/krum.py +1 -1
  175. flwr/server/strategy/qfedavg.py +1 -1
  176. flwr/server/superlink/driver/__init__.py +1 -1
  177. flwr/server/superlink/driver/driver_grpc.py +1 -1
  178. flwr/server/superlink/driver/driver_servicer.py +51 -4
  179. flwr/server/superlink/ffs/__init__.py +24 -0
  180. flwr/server/superlink/ffs/disk_ffs.py +104 -0
  181. flwr/server/superlink/ffs/ffs.py +79 -0
  182. flwr/server/superlink/fleet/__init__.py +1 -1
  183. flwr/server/superlink/fleet/grpc_adapter/__init__.py +15 -0
  184. flwr/server/superlink/fleet/grpc_adapter/grpc_adapter_servicer.py +131 -0
  185. flwr/server/superlink/fleet/grpc_bidi/__init__.py +1 -1
  186. flwr/server/superlink/fleet/grpc_bidi/flower_service_servicer.py +1 -1
  187. flwr/server/superlink/fleet/grpc_bidi/grpc_bridge.py +1 -1
  188. flwr/server/superlink/fleet/grpc_bidi/grpc_client_proxy.py +1 -1
  189. flwr/server/superlink/fleet/grpc_bidi/grpc_server.py +8 -2
  190. flwr/server/superlink/fleet/grpc_rere/__init__.py +1 -1
  191. flwr/server/superlink/fleet/grpc_rere/fleet_servicer.py +30 -2
  192. flwr/server/superlink/fleet/grpc_rere/server_interceptor.py +214 -0
  193. flwr/server/superlink/fleet/message_handler/__init__.py +1 -1
  194. flwr/server/superlink/fleet/message_handler/message_handler.py +42 -2
  195. flwr/server/superlink/fleet/rest_rere/__init__.py +1 -1
  196. flwr/server/superlink/fleet/rest_rere/rest_api.py +59 -1
  197. flwr/server/superlink/fleet/vce/backend/__init__.py +1 -1
  198. flwr/server/superlink/fleet/vce/backend/backend.py +5 -5
  199. flwr/server/superlink/fleet/vce/backend/raybackend.py +53 -56
  200. flwr/server/superlink/fleet/vce/vce_api.py +190 -127
  201. flwr/server/superlink/state/__init__.py +1 -1
  202. flwr/server/superlink/state/in_memory_state.py +159 -42
  203. flwr/server/superlink/state/sqlite_state.py +243 -39
  204. flwr/server/superlink/state/state.py +81 -6
  205. flwr/server/superlink/state/state_factory.py +11 -2
  206. flwr/server/superlink/state/utils.py +62 -0
  207. flwr/server/typing.py +2 -0
  208. flwr/server/utils/__init__.py +1 -1
  209. flwr/server/utils/tensorboard.py +1 -1
  210. flwr/server/utils/validator.py +23 -9
  211. flwr/server/workflow/default_workflows.py +67 -25
  212. flwr/server/workflow/secure_aggregation/secaggplus_workflow.py +18 -6
  213. flwr/simulation/__init__.py +7 -4
  214. flwr/simulation/app.py +67 -36
  215. flwr/simulation/ray_transport/__init__.py +1 -1
  216. flwr/simulation/ray_transport/ray_actor.py +20 -46
  217. flwr/simulation/ray_transport/ray_client_proxy.py +36 -16
  218. flwr/simulation/run_simulation.py +308 -92
  219. flwr/superexec/__init__.py +21 -0
  220. flwr/superexec/app.py +184 -0
  221. flwr/superexec/deployment.py +185 -0
  222. flwr/superexec/exec_grpc.py +55 -0
  223. flwr/superexec/exec_servicer.py +70 -0
  224. flwr/superexec/executor.py +75 -0
  225. flwr/superexec/simulation.py +193 -0
  226. {flwr_nightly-1.8.0.dev20240315.dist-info → flwr_nightly-1.11.0.dev20240813.dist-info}/METADATA +10 -6
  227. flwr_nightly-1.11.0.dev20240813.dist-info/RECORD +288 -0
  228. flwr_nightly-1.11.0.dev20240813.dist-info/entry_points.txt +10 -0
  229. flwr/cli/flower_toml.py +0 -140
  230. flwr/cli/new/templates/app/flower.toml.tpl +0 -13
  231. flwr/cli/new/templates/app/requirements.numpy.txt.tpl +0 -2
  232. flwr/cli/new/templates/app/requirements.pytorch.txt.tpl +0 -4
  233. flwr/cli/new/templates/app/requirements.tensorflow.txt.tpl +0 -4
  234. flwr_nightly-1.8.0.dev20240315.dist-info/RECORD +0 -211
  235. flwr_nightly-1.8.0.dev20240315.dist-info/entry_points.txt +0 -9
  236. {flwr_nightly-1.8.0.dev20240315.dist-info → flwr_nightly-1.11.0.dev20240813.dist-info}/LICENSE +0 -0
  237. {flwr_nightly-1.8.0.dev20240315.dist-info → flwr_nightly-1.11.0.dev20240813.dist-info}/WHEEL +0 -0
flwr/superexec/app.py ADDED
@@ -0,0 +1,184 @@
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 SuperExec app."""
16
+
17
+ import argparse
18
+ import sys
19
+ from logging import INFO, WARN
20
+ from pathlib import Path
21
+ from typing import Optional, Tuple
22
+
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 SUPEREXEC_DEFAULT_ADDRESS
29
+ from flwr.common.exit_handlers import register_exit_handlers
30
+ from flwr.common.object_ref import load_app, validate
31
+
32
+ from .exec_grpc import run_superexec_api_grpc
33
+ from .executor import Executor
34
+
35
+
36
+ def run_superexec() -> None:
37
+ """Run Flower SuperExec."""
38
+ log(INFO, "Starting Flower SuperExec")
39
+
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([args.executor_config]),
60
+ )
61
+
62
+ grpc_servers = [superexec_server]
63
+
64
+ # Graceful shutdown
65
+ register_exit_handlers(
66
+ event_type=EventType.RUN_SUPEREXEC_LEAVE,
67
+ grpc_servers=grpc_servers,
68
+ bckg_threads=None,
69
+ )
70
+
71
+ superexec_server.wait_for_termination()
72
+
73
+
74
+ def _parse_args_run_superexec() -> argparse.ArgumentParser:
75
+ """Parse command line arguments for SuperExec."""
76
+ parser = argparse.ArgumentParser(
77
+ description="Start a Flower SuperExec",
78
+ )
79
+ parser.add_argument(
80
+ "--address",
81
+ help="SuperExec (gRPC) server address (IPv4, IPv6, or a domain name)",
82
+ default=SUPEREXEC_DEFAULT_ADDRESS,
83
+ )
84
+ parser.add_argument(
85
+ "--executor",
86
+ help="For example: `deployment:exec` or `project.package.module:wrapper.exec`.",
87
+ default="flwr.superexec.deployment:executor",
88
+ )
89
+ parser.add_argument(
90
+ "--executor-dir",
91
+ help="The directory for the executor.",
92
+ default=".",
93
+ )
94
+ parser.add_argument(
95
+ "--executor-config",
96
+ help="Key-value pairs for the executor config, separated by commas. "
97
+ 'For example:\n\n`--executor-config superlink="superlink:9091",'
98
+ 'root-certificates="certificates/superlink-ca.crt"`',
99
+ )
100
+ parser.add_argument(
101
+ "--insecure",
102
+ action="store_true",
103
+ help="Run the SuperExec without HTTPS, regardless of whether certificate "
104
+ "paths are provided. By default, the server runs with HTTPS enabled. "
105
+ "Use this flag only if you understand the risks.",
106
+ )
107
+ parser.add_argument(
108
+ "--ssl-certfile",
109
+ help="SuperExec server SSL certificate file (as a path str) "
110
+ "to create a secure connection.",
111
+ type=str,
112
+ default=None,
113
+ )
114
+ parser.add_argument(
115
+ "--ssl-keyfile",
116
+ help="SuperExec server SSL private key file (as a path str) "
117
+ "to create a secure connection.",
118
+ type=str,
119
+ )
120
+ parser.add_argument(
121
+ "--ssl-ca-certfile",
122
+ help="SuperExec server SSL CA certificate file (as a path str) "
123
+ "to create a secure connection.",
124
+ type=str,
125
+ )
126
+ return parser
127
+
128
+
129
+ def _try_obtain_certificates(
130
+ args: argparse.Namespace,
131
+ ) -> Optional[Tuple[bytes, bytes, bytes]]:
132
+ # Obtain certificates
133
+ if args.insecure:
134
+ log(WARN, "Option `--insecure` was set. Starting insecure HTTP server.")
135
+ return None
136
+ # Check if certificates are provided
137
+ if args.ssl_certfile and args.ssl_keyfile and args.ssl_ca_certfile:
138
+ if not Path(args.ssl_ca_certfile).is_file():
139
+ sys.exit("Path argument `--ssl-ca-certfile` does not point to a file.")
140
+ if not Path(args.ssl_certfile).is_file():
141
+ sys.exit("Path argument `--ssl-certfile` does not point to a file.")
142
+ if not Path(args.ssl_keyfile).is_file():
143
+ sys.exit("Path argument `--ssl-keyfile` does not point to a file.")
144
+ certificates = (
145
+ Path(args.ssl_ca_certfile).read_bytes(), # CA certificate
146
+ Path(args.ssl_certfile).read_bytes(), # server certificate
147
+ Path(args.ssl_keyfile).read_bytes(), # server private key
148
+ )
149
+ return certificates
150
+ if args.ssl_certfile or args.ssl_keyfile or args.ssl_ca_certfile:
151
+ sys.exit(
152
+ "You need to provide valid file paths to `--ssl-certfile`, "
153
+ "`--ssl-keyfile`, and `—-ssl-ca-certfile` to create a secure "
154
+ "connection in SuperExec server (gRPC-rere)."
155
+ )
156
+ sys.exit(
157
+ "Certificates are required unless running in insecure mode. "
158
+ "Please provide certificate paths to `--ssl-certfile`, "
159
+ "`--ssl-keyfile`, and `—-ssl-ca-certfile` or run the server "
160
+ "in insecure mode using '--insecure' if you understand the risks."
161
+ )
162
+
163
+
164
+ def _load_executor(
165
+ args: argparse.Namespace,
166
+ ) -> Executor:
167
+ """Get the executor plugin."""
168
+ executor_ref: str = args.executor
169
+ valid, error_msg = validate(executor_ref, project_dir=args.executor_dir)
170
+ if not valid and error_msg:
171
+ raise LoadExecutorError(error_msg) from None
172
+
173
+ executor = load_app(executor_ref, LoadExecutorError, args.executor_dir)
174
+
175
+ if not isinstance(executor, Executor):
176
+ raise LoadExecutorError(
177
+ f"Attribute {executor_ref} is not of type {Executor}",
178
+ ) from None
179
+
180
+ return executor
181
+
182
+
183
+ class LoadExecutorError(Exception):
184
+ """Error when trying to load `Executor`."""
@@ -0,0 +1,185 @@
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
+ """Deployment engine executor."""
16
+
17
+ import subprocess
18
+ from logging import ERROR, INFO
19
+ from pathlib import Path
20
+ from typing import Optional
21
+
22
+ from typing_extensions import override
23
+
24
+ from flwr.cli.config_utils import get_fab_metadata
25
+ from flwr.cli.install import install_from_fab
26
+ from flwr.common.grpc import create_channel
27
+ from flwr.common.logger import log
28
+ from flwr.common.serde import user_config_to_proto
29
+ from flwr.common.typing import UserConfig
30
+ from flwr.proto.driver_pb2 import CreateRunRequest # pylint: disable=E0611
31
+ from flwr.proto.driver_pb2_grpc import DriverStub
32
+ from flwr.server.driver.grpc_driver import DEFAULT_SERVER_ADDRESS_DRIVER
33
+
34
+ from .executor import Executor, RunTracker
35
+
36
+
37
+ class DeploymentEngine(Executor):
38
+ """Deployment engine executor.
39
+
40
+ Parameters
41
+ ----------
42
+ superlink: str (default: "0.0.0.0:9091")
43
+ Address of the SuperLink to connect to.
44
+ root_certificates: Optional[str] (default: None)
45
+ Specifies the path to the PEM-encoded root certificate file for
46
+ establishing secure HTTPS connections.
47
+ flwr_dir: Optional[str] (default: None)
48
+ The path containing installed Flower Apps.
49
+ """
50
+
51
+ def __init__(
52
+ self,
53
+ superlink: str = DEFAULT_SERVER_ADDRESS_DRIVER,
54
+ root_certificates: Optional[str] = None,
55
+ flwr_dir: Optional[str] = None,
56
+ ) -> None:
57
+ self.superlink = superlink
58
+ if root_certificates is None:
59
+ self.root_certificates = None
60
+ self.root_certificates_bytes = None
61
+ else:
62
+ self.root_certificates = root_certificates
63
+ self.root_certificates_bytes = Path(root_certificates).read_bytes()
64
+ self.flwr_dir = flwr_dir
65
+ self.stub: Optional[DriverStub] = None
66
+
67
+ @override
68
+ def set_config(
69
+ self,
70
+ config: UserConfig,
71
+ ) -> None:
72
+ """Set executor config arguments.
73
+
74
+ Parameters
75
+ ----------
76
+ config : UserConfig
77
+ A dictionary for configuration values.
78
+ Supported configuration key/value pairs:
79
+ - "superlink": str
80
+ The address of the SuperLink Driver API.
81
+ - "root-certificates": str
82
+ The path to the root certificates.
83
+ - "flwr-dir": str
84
+ The path to the Flower directory.
85
+ """
86
+ if not config:
87
+ return
88
+ if superlink_address := config.get("superlink"):
89
+ if not isinstance(superlink_address, str):
90
+ raise ValueError("The `superlink` value should be of type `str`.")
91
+ self.superlink = superlink_address
92
+ if root_certificates := config.get("root-certificates"):
93
+ if not isinstance(root_certificates, str):
94
+ raise ValueError(
95
+ "The `root-certificates` value should be of type `str`."
96
+ )
97
+ self.root_certificates = root_certificates
98
+ self.root_certificates_bytes = Path(str(root_certificates)).read_bytes()
99
+ if flwr_dir := config.get("flwr-dir"):
100
+ if not isinstance(flwr_dir, str):
101
+ raise ValueError("The `flwr-dir` value should be of type `str`.")
102
+ self.flwr_dir = str(flwr_dir)
103
+
104
+ def _connect(self) -> None:
105
+ if self.stub is not None:
106
+ return
107
+ channel = create_channel(
108
+ server_address=self.superlink,
109
+ insecure=(self.root_certificates_bytes is None),
110
+ root_certificates=self.root_certificates_bytes,
111
+ )
112
+ self.stub = DriverStub(channel)
113
+
114
+ def _create_run(
115
+ self,
116
+ fab_id: str,
117
+ fab_version: str,
118
+ override_config: UserConfig,
119
+ ) -> int:
120
+ if self.stub is None:
121
+ self._connect()
122
+
123
+ assert self.stub is not None
124
+
125
+ req = CreateRunRequest(
126
+ fab_id=fab_id,
127
+ fab_version=fab_version,
128
+ override_config=user_config_to_proto(override_config),
129
+ )
130
+ res = self.stub.CreateRun(request=req)
131
+ return int(res.run_id)
132
+
133
+ @override
134
+ def start_run(
135
+ self,
136
+ fab_file: bytes,
137
+ override_config: UserConfig,
138
+ federation_config: UserConfig,
139
+ ) -> Optional[RunTracker]:
140
+ """Start run using the Flower Deployment Engine."""
141
+ try:
142
+ # Install FAB to flwr dir
143
+ fab_version, fab_id = get_fab_metadata(fab_file)
144
+ install_from_fab(fab_file, None, True)
145
+
146
+ # Call SuperLink to create run
147
+ run_id: int = self._create_run(fab_id, fab_version, override_config)
148
+ log(INFO, "Created run %s", str(run_id))
149
+
150
+ command = [
151
+ "flower-server-app",
152
+ "--run-id",
153
+ str(run_id),
154
+ "--superlink",
155
+ str(self.superlink),
156
+ ]
157
+
158
+ if self.flwr_dir:
159
+ command.append("--flwr-dir")
160
+ command.append(self.flwr_dir)
161
+
162
+ if self.root_certificates is None:
163
+ command.append("--insecure")
164
+ else:
165
+ command.append("--root-certificates")
166
+ command.append(self.root_certificates)
167
+
168
+ # Execute the command
169
+ proc = subprocess.Popen( # pylint: disable=consider-using-with
170
+ command,
171
+ text=True,
172
+ )
173
+ log(INFO, "Started run %s", str(run_id))
174
+
175
+ return RunTracker(
176
+ run_id=run_id,
177
+ proc=proc,
178
+ )
179
+ # pylint: disable-next=broad-except
180
+ except Exception as e:
181
+ log(ERROR, "Could not start run: %s", str(e))
182
+ return None
183
+
184
+
185
+ executor = DeploymentEngine()
@@ -0,0 +1,55 @@
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
+ """SuperExec gRPC API."""
16
+
17
+ from logging import INFO
18
+ from typing import Optional, Tuple
19
+
20
+ import grpc
21
+
22
+ from flwr.common import GRPC_MAX_MESSAGE_LENGTH
23
+ from flwr.common.logger import log
24
+ from flwr.common.typing import UserConfig
25
+ from flwr.proto.exec_pb2_grpc import add_ExecServicer_to_server
26
+ from flwr.server.superlink.fleet.grpc_bidi.grpc_server import generic_create_grpc_server
27
+
28
+ from .exec_servicer import ExecServicer
29
+ from .executor import Executor
30
+
31
+
32
+ def run_superexec_api_grpc(
33
+ address: str,
34
+ executor: Executor,
35
+ certificates: Optional[Tuple[bytes, bytes, bytes]],
36
+ config: UserConfig,
37
+ ) -> grpc.Server:
38
+ """Run SuperExec API (gRPC, request-response)."""
39
+ executor.set_config(config)
40
+
41
+ exec_servicer: grpc.Server = ExecServicer(
42
+ executor=executor,
43
+ )
44
+ superexec_add_servicer_to_server_fn = add_ExecServicer_to_server
45
+ superexec_grpc_server = generic_create_grpc_server(
46
+ servicer_and_add_fn=(exec_servicer, superexec_add_servicer_to_server_fn),
47
+ server_address=address,
48
+ max_message_length=GRPC_MAX_MESSAGE_LENGTH,
49
+ certificates=certificates,
50
+ )
51
+
52
+ log(INFO, "Starting Flower SuperExec gRPC server on %s", address)
53
+ superexec_grpc_server.start()
54
+
55
+ return superexec_grpc_server
@@ -0,0 +1,70 @@
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
+ """SuperExec API servicer."""
16
+
17
+
18
+ from logging import ERROR, INFO
19
+ from typing import Any, Dict, Generator
20
+
21
+ import grpc
22
+
23
+ from flwr.common.logger import log
24
+ from flwr.common.serde import user_config_from_proto
25
+ from flwr.proto import exec_pb2_grpc # pylint: disable=E0611
26
+ from flwr.proto.exec_pb2 import ( # pylint: disable=E0611
27
+ StartRunRequest,
28
+ StartRunResponse,
29
+ StreamLogsRequest,
30
+ StreamLogsResponse,
31
+ )
32
+
33
+ from .executor import Executor, RunTracker
34
+
35
+
36
+ class ExecServicer(exec_pb2_grpc.ExecServicer):
37
+ """SuperExec API servicer."""
38
+
39
+ def __init__(self, executor: Executor) -> None:
40
+ self.executor = executor
41
+ self.runs: Dict[int, RunTracker] = {}
42
+
43
+ def StartRun(
44
+ self, request: StartRunRequest, context: grpc.ServicerContext
45
+ ) -> StartRunResponse:
46
+ """Create run ID."""
47
+ log(INFO, "ExecServicer.StartRun")
48
+
49
+ run = self.executor.start_run(
50
+ request.fab_file,
51
+ user_config_from_proto(request.override_config),
52
+ user_config_from_proto(request.federation_config),
53
+ )
54
+
55
+ if run is None:
56
+ log(ERROR, "Executor failed to start run")
57
+ return StartRunResponse()
58
+
59
+ self.runs[run.run_id] = run
60
+
61
+ return StartRunResponse(run_id=run.run_id)
62
+
63
+ def StreamLogs(
64
+ self, request: StreamLogsRequest, context: grpc.ServicerContext
65
+ ) -> Generator[StreamLogsResponse, Any, None]:
66
+ """Get logs."""
67
+ logs = ["a", "b", "c"]
68
+ while context.is_active():
69
+ for i in range(len(logs)): # pylint: disable=C0200
70
+ yield StreamLogsResponse(log_output=logs[i])
@@ -0,0 +1,75 @@
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
+ """Execute and monitor a Flower run."""
16
+
17
+ from abc import ABC, abstractmethod
18
+ from dataclasses import dataclass
19
+ from subprocess import Popen
20
+ from typing import Optional
21
+
22
+ from flwr.common.typing import UserConfig
23
+
24
+
25
+ @dataclass
26
+ class RunTracker:
27
+ """Track a Flower run (composed of a run_id and the associated process)."""
28
+
29
+ run_id: int
30
+ proc: Popen # type: ignore
31
+
32
+
33
+ class Executor(ABC):
34
+ """Execute and monitor a Flower run."""
35
+
36
+ @abstractmethod
37
+ def set_config(
38
+ self,
39
+ config: UserConfig,
40
+ ) -> None:
41
+ """Register provided config as class attributes.
42
+
43
+ Parameters
44
+ ----------
45
+ config : UserConfig
46
+ A dictionary for configuration values.
47
+ """
48
+
49
+ @abstractmethod
50
+ def start_run(
51
+ self,
52
+ fab_file: bytes,
53
+ override_config: UserConfig,
54
+ federation_config: UserConfig,
55
+ ) -> Optional[RunTracker]:
56
+ """Start a run using the given Flower FAB ID and version.
57
+
58
+ This method creates a new run on the SuperLink, returns its run_id
59
+ and also starts the run execution.
60
+
61
+ Parameters
62
+ ----------
63
+ fab_file : bytes
64
+ The Flower App Bundle file bytes.
65
+ override_config: UserConfig
66
+ The config overrides dict sent by the user (using `flwr run`).
67
+ federation_config: UserConfig
68
+ The federation options dict sent by the user (using `flwr run`).
69
+
70
+ Returns
71
+ -------
72
+ run_id : Optional[RunTracker]
73
+ The run_id and the associated process of the run created by the SuperLink,
74
+ or `None` if it fails.
75
+ """