flwr-nightly 1.11.0.dev20240822__py3-none-any.whl → 1.11.1.dev20240912__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 +0 -2
- flwr/cli/build.py +1 -1
- flwr/cli/new/new.py +41 -40
- flwr/cli/new/templates/app/LICENSE.tpl +202 -0
- flwr/cli/new/templates/app/README.baseline.md.tpl +127 -0
- flwr/cli/new/templates/app/README.flowertune.md.tpl +16 -6
- flwr/cli/new/templates/app/README.md.tpl +7 -30
- flwr/cli/new/templates/app/code/__init__.baseline.py.tpl +1 -0
- flwr/cli/new/templates/app/code/client.baseline.py.tpl +58 -0
- flwr/cli/new/templates/app/code/client.huggingface.py.tpl +19 -29
- flwr/cli/new/templates/app/code/client.tensorflow.py.tpl +0 -3
- flwr/cli/new/templates/app/code/dataset.baseline.py.tpl +36 -0
- flwr/cli/new/templates/app/code/flwr_tune/{client.py.tpl → client_app.py.tpl} +50 -40
- flwr/cli/new/templates/app/code/flwr_tune/dataset.py.tpl +32 -2
- flwr/cli/new/templates/app/code/flwr_tune/models.py.tpl +0 -3
- flwr/cli/new/templates/app/code/flwr_tune/server_app.py.tpl +95 -0
- flwr/cli/new/templates/app/code/flwr_tune/strategy.py.tpl +83 -0
- flwr/cli/new/templates/app/code/model.baseline.py.tpl +80 -0
- flwr/cli/new/templates/app/code/server.baseline.py.tpl +46 -0
- flwr/cli/new/templates/app/code/server.huggingface.py.tpl +18 -3
- flwr/cli/new/templates/app/code/strategy.baseline.py.tpl +1 -0
- flwr/cli/new/templates/app/code/task.huggingface.py.tpl +16 -13
- flwr/cli/new/templates/app/code/utils.baseline.py.tpl +1 -0
- flwr/cli/new/templates/app/pyproject.baseline.toml.tpl +138 -0
- flwr/cli/new/templates/app/pyproject.flowertune.toml.tpl +34 -7
- flwr/cli/new/templates/app/pyproject.huggingface.toml.tpl +9 -1
- flwr/cli/run/run.py +12 -2
- flwr/client/__init__.py +0 -4
- flwr/client/app.py +3 -4
- flwr/client/client.py +22 -1
- flwr/client/client_app.py +2 -2
- flwr/client/grpc_rere_client/client_interceptor.py +15 -7
- flwr/client/numpy_client.py +22 -1
- flwr/client/rest_client/connection.py +1 -1
- flwr/client/supernode/app.py +8 -7
- flwr/common/address.py +43 -0
- flwr/common/config.py +14 -11
- flwr/common/constant.py +12 -1
- flwr/common/record/recordset.py +1 -1
- flwr/common/record/typeddict.py +24 -1
- flwr/common/telemetry.py +36 -30
- flwr/server/__init__.py +0 -4
- flwr/server/app.py +27 -22
- flwr/server/compat/app.py +0 -5
- flwr/server/driver/grpc_driver.py +3 -6
- flwr/server/run_serverapp.py +20 -7
- flwr/server/superlink/fleet/grpc_adapter/grpc_adapter_servicer.py +15 -2
- flwr/server/superlink/fleet/grpc_bidi/grpc_server.py +5 -0
- flwr/server/superlink/fleet/grpc_rere/fleet_servicer.py +19 -8
- flwr/server/superlink/fleet/grpc_rere/server_interceptor.py +13 -12
- flwr/server/superlink/fleet/rest_rere/rest_api.py +71 -122
- flwr/server/superlink/fleet/vce/backend/backend.py +1 -2
- flwr/server/superlink/fleet/vce/backend/raybackend.py +33 -15
- flwr/server/superlink/fleet/vce/vce_api.py +2 -6
- flwr/server/superlink/state/in_memory_state.py +15 -15
- flwr/server/superlink/state/sqlite_state.py +10 -10
- flwr/server/superlink/state/state.py +8 -8
- flwr/server/workflow/secure_aggregation/secagg_workflow.py +1 -0
- flwr/server/workflow/secure_aggregation/secaggplus_workflow.py +1 -0
- flwr/simulation/ray_transport/ray_actor.py +2 -2
- flwr/simulation/run_simulation.py +85 -25
- flwr/superexec/__init__.py +0 -6
- flwr/superexec/app.py +5 -3
- flwr/superexec/deployment.py +2 -2
- flwr/superexec/simulation.py +20 -1
- {flwr_nightly-1.11.0.dev20240822.dist-info → flwr_nightly-1.11.1.dev20240912.dist-info}/METADATA +3 -3
- {flwr_nightly-1.11.0.dev20240822.dist-info → flwr_nightly-1.11.1.dev20240912.dist-info}/RECORD +70 -62
- flwr_nightly-1.11.1.dev20240912.dist-info/entry_points.txt +10 -0
- flwr/cli/new/templates/app/code/flwr_tune/app.py.tpl +0 -89
- flwr/cli/new/templates/app/code/flwr_tune/config.yaml.tpl +0 -34
- flwr/cli/new/templates/app/code/flwr_tune/server.py.tpl +0 -48
- flwr/cli/new/templates/app/code/flwr_tune/static_config.yaml.tpl +0 -11
- flwr_nightly-1.11.0.dev20240822.dist-info/entry_points.txt +0 -10
- {flwr_nightly-1.11.0.dev20240822.dist-info → flwr_nightly-1.11.1.dev20240912.dist-info}/LICENSE +0 -0
- {flwr_nightly-1.11.0.dev20240822.dist-info → flwr_nightly-1.11.1.dev20240912.dist-info}/WHEEL +0 -0
flwr/client/supernode/app.py
CHANGED
|
@@ -30,6 +30,7 @@ from cryptography.hazmat.primitives.serialization import (
|
|
|
30
30
|
from flwr.common import EventType, event
|
|
31
31
|
from flwr.common.config import parse_config_args
|
|
32
32
|
from flwr.common.constant import (
|
|
33
|
+
FLEET_API_GRPC_RERE_DEFAULT_ADDRESS,
|
|
33
34
|
TRANSPORT_TYPE_GRPC_ADAPTER,
|
|
34
35
|
TRANSPORT_TYPE_GRPC_RERE,
|
|
35
36
|
TRANSPORT_TYPE_REST,
|
|
@@ -44,8 +45,6 @@ from ..app import (
|
|
|
44
45
|
)
|
|
45
46
|
from ..clientapp.utils import get_load_client_app_fn
|
|
46
47
|
|
|
47
|
-
ADDRESS_FLEET_API_GRPC_RERE = "0.0.0.0:9092"
|
|
48
|
-
|
|
49
48
|
|
|
50
49
|
def run_supernode() -> None:
|
|
51
50
|
"""Run Flower SuperNode."""
|
|
@@ -77,7 +76,9 @@ def run_supernode() -> None:
|
|
|
77
76
|
authentication_keys=authentication_keys,
|
|
78
77
|
max_retries=args.max_retries,
|
|
79
78
|
max_wait_time=args.max_wait_time,
|
|
80
|
-
node_config=parse_config_args(
|
|
79
|
+
node_config=parse_config_args(
|
|
80
|
+
[args.node_config] if args.node_config else args.node_config
|
|
81
|
+
),
|
|
81
82
|
isolation=args.isolation,
|
|
82
83
|
supernode_address=args.supernode_address,
|
|
83
84
|
)
|
|
@@ -101,11 +102,11 @@ def run_client_app() -> None:
|
|
|
101
102
|
|
|
102
103
|
def _warn_deprecated_server_arg(args: argparse.Namespace) -> None:
|
|
103
104
|
"""Warn about the deprecated argument `--server`."""
|
|
104
|
-
if args.server !=
|
|
105
|
+
if args.server != FLEET_API_GRPC_RERE_DEFAULT_ADDRESS:
|
|
105
106
|
warn = "Passing flag --server is deprecated. Use --superlink instead."
|
|
106
107
|
warn_deprecated_feature(warn)
|
|
107
108
|
|
|
108
|
-
if args.superlink !=
|
|
109
|
+
if args.superlink != FLEET_API_GRPC_RERE_DEFAULT_ADDRESS:
|
|
109
110
|
# if `--superlink` also passed, then
|
|
110
111
|
# warn user that this argument overrides what was passed with `--server`
|
|
111
112
|
log(
|
|
@@ -245,12 +246,12 @@ def _parse_args_common(parser: argparse.ArgumentParser) -> None:
|
|
|
245
246
|
)
|
|
246
247
|
parser.add_argument(
|
|
247
248
|
"--server",
|
|
248
|
-
default=
|
|
249
|
+
default=FLEET_API_GRPC_RERE_DEFAULT_ADDRESS,
|
|
249
250
|
help="Server address",
|
|
250
251
|
)
|
|
251
252
|
parser.add_argument(
|
|
252
253
|
"--superlink",
|
|
253
|
-
default=
|
|
254
|
+
default=FLEET_API_GRPC_RERE_DEFAULT_ADDRESS,
|
|
254
255
|
help="SuperLink Fleet API (gRPC-rere) address (IPv4, IPv6, or a domain name)",
|
|
255
256
|
)
|
|
256
257
|
parser.add_argument(
|
flwr/common/address.py
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
# ==============================================================================
|
|
15
15
|
"""Flower IP address utils."""
|
|
16
16
|
|
|
17
|
+
import socket
|
|
17
18
|
from ipaddress import ip_address
|
|
18
19
|
from typing import Optional, Tuple
|
|
19
20
|
|
|
@@ -57,3 +58,45 @@ def parse_address(address: str) -> Optional[Tuple[str, int, Optional[bool]]]:
|
|
|
57
58
|
|
|
58
59
|
except ValueError:
|
|
59
60
|
return None
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def is_port_in_use(address: str) -> bool:
|
|
64
|
+
"""Check if the port specified in address is in use.
|
|
65
|
+
|
|
66
|
+
Parameters
|
|
67
|
+
----------
|
|
68
|
+
address : str
|
|
69
|
+
The string representation of a domain, an IPv4, or an IPV6 address
|
|
70
|
+
with the port number.
|
|
71
|
+
|
|
72
|
+
For example, '127.0.0.1:8080', or `[::1]:8080`.
|
|
73
|
+
|
|
74
|
+
Returns
|
|
75
|
+
-------
|
|
76
|
+
bool
|
|
77
|
+
If the port provided is in use or can't be parsed,
|
|
78
|
+
the function will return True, otherwise it will return False.
|
|
79
|
+
"""
|
|
80
|
+
parsed_address = parse_address(address)
|
|
81
|
+
if not parsed_address:
|
|
82
|
+
return True
|
|
83
|
+
host, port, is_v6 = parsed_address
|
|
84
|
+
|
|
85
|
+
if is_v6:
|
|
86
|
+
protocol = socket.AF_INET6
|
|
87
|
+
else:
|
|
88
|
+
protocol = socket.AF_INET
|
|
89
|
+
|
|
90
|
+
with socket.socket(protocol, socket.SOCK_STREAM) as s:
|
|
91
|
+
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
|
92
|
+
try:
|
|
93
|
+
if is_v6:
|
|
94
|
+
# For IPv6, provide `flowinfo` and `scopeid` as 0
|
|
95
|
+
s.bind((host, port, 0, 0))
|
|
96
|
+
else:
|
|
97
|
+
# For IPv4
|
|
98
|
+
s.bind((host, port))
|
|
99
|
+
except OSError:
|
|
100
|
+
return True
|
|
101
|
+
|
|
102
|
+
return False
|
flwr/common/config.py
CHANGED
|
@@ -185,23 +185,26 @@ def parse_config_args(
|
|
|
185
185
|
if config is None:
|
|
186
186
|
return overrides
|
|
187
187
|
|
|
188
|
+
# Handle if .toml file is passed
|
|
189
|
+
if len(config) == 1 and config[0].endswith(".toml"):
|
|
190
|
+
with Path(config[0]).open("rb") as config_file:
|
|
191
|
+
overrides = flatten_dict(tomli.load(config_file))
|
|
192
|
+
return overrides
|
|
193
|
+
|
|
188
194
|
# Regular expression to capture key-value pairs with possible quoted values
|
|
189
195
|
pattern = re.compile(r"(\S+?)=(\'[^\']*\'|\"[^\"]*\"|\S+)")
|
|
190
196
|
|
|
191
197
|
for config_line in config:
|
|
192
198
|
if config_line:
|
|
193
|
-
|
|
199
|
+
# .toml files aren't allowed alongside other configs
|
|
200
|
+
if config_line.endswith(".toml"):
|
|
201
|
+
raise ValueError(
|
|
202
|
+
"TOML files cannot be passed alongside key-value pairs."
|
|
203
|
+
)
|
|
194
204
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
and matches[0][0].endswith(".toml")
|
|
199
|
-
):
|
|
200
|
-
with Path(matches[0][0]).open("rb") as config_file:
|
|
201
|
-
overrides = flatten_dict(tomli.load(config_file))
|
|
202
|
-
else:
|
|
203
|
-
toml_str = "\n".join(f"{k} = {v}" for k, v in matches)
|
|
204
|
-
overrides.update(tomli.loads(toml_str))
|
|
205
|
+
matches = pattern.findall(config_line)
|
|
206
|
+
toml_str = "\n".join(f"{k} = {v}" for k, v in matches)
|
|
207
|
+
overrides.update(tomli.loads(toml_str))
|
|
205
208
|
|
|
206
209
|
return overrides
|
|
207
210
|
|
flwr/common/constant.py
CHANGED
|
@@ -37,7 +37,18 @@ TRANSPORT_TYPES = [
|
|
|
37
37
|
TRANSPORT_TYPE_VCE,
|
|
38
38
|
]
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
# Addresses
|
|
41
|
+
# SuperNode
|
|
42
|
+
CLIENTAPPIO_API_DEFAULT_ADDRESS = "0.0.0.0:9094"
|
|
43
|
+
# SuperExec
|
|
44
|
+
EXEC_API_DEFAULT_ADDRESS = "0.0.0.0:9093"
|
|
45
|
+
# SuperLink
|
|
46
|
+
DRIVER_API_DEFAULT_ADDRESS = "0.0.0.0:9091"
|
|
47
|
+
FLEET_API_GRPC_RERE_DEFAULT_ADDRESS = "0.0.0.0:9092"
|
|
48
|
+
FLEET_API_GRPC_BIDI_DEFAULT_ADDRESS = (
|
|
49
|
+
"[::]:8080" # IPv6 to keep start_server compatible
|
|
50
|
+
)
|
|
51
|
+
FLEET_API_REST_DEFAULT_ADDRESS = "0.0.0.0:9093"
|
|
41
52
|
|
|
42
53
|
# Constants for ping
|
|
43
54
|
PING_DEFAULT_INTERVAL = 30
|
flwr/common/record/recordset.py
CHANGED
|
@@ -119,7 +119,7 @@ class RecordSet:
|
|
|
119
119
|
Let's see an example.
|
|
120
120
|
|
|
121
121
|
>>> from flwr.common import RecordSet
|
|
122
|
-
>>> from flwr.common import
|
|
122
|
+
>>> from flwr.common import ConfigsRecord, MetricsRecord, ParametersRecord
|
|
123
123
|
>>>
|
|
124
124
|
>>> # Let's begin with an empty record
|
|
125
125
|
>>> my_recordset = RecordSet()
|
flwr/common/record/typeddict.py
CHANGED
|
@@ -15,7 +15,18 @@
|
|
|
15
15
|
"""Typed dict base class for *Records."""
|
|
16
16
|
|
|
17
17
|
|
|
18
|
-
from typing import
|
|
18
|
+
from typing import (
|
|
19
|
+
Callable,
|
|
20
|
+
Dict,
|
|
21
|
+
Generic,
|
|
22
|
+
ItemsView,
|
|
23
|
+
Iterator,
|
|
24
|
+
KeysView,
|
|
25
|
+
MutableMapping,
|
|
26
|
+
TypeVar,
|
|
27
|
+
ValuesView,
|
|
28
|
+
cast,
|
|
29
|
+
)
|
|
19
30
|
|
|
20
31
|
K = TypeVar("K") # Key type
|
|
21
32
|
V = TypeVar("V") # Value type
|
|
@@ -73,3 +84,15 @@ class TypedDict(MutableMapping[K, V], Generic[K, V]):
|
|
|
73
84
|
if isinstance(other, dict):
|
|
74
85
|
return data == other
|
|
75
86
|
return NotImplemented
|
|
87
|
+
|
|
88
|
+
def keys(self) -> KeysView[K]:
|
|
89
|
+
"""D.keys() -> a set-like object providing a view on D's keys."""
|
|
90
|
+
return cast(Dict[K, V], self.__dict__["_data"]).keys()
|
|
91
|
+
|
|
92
|
+
def values(self) -> ValuesView[V]:
|
|
93
|
+
"""D.values() -> an object providing a view on D's values."""
|
|
94
|
+
return cast(Dict[K, V], self.__dict__["_data"]).values()
|
|
95
|
+
|
|
96
|
+
def items(self) -> ItemsView[K, V]:
|
|
97
|
+
"""D.items() -> a set-like object providing a view on D's items."""
|
|
98
|
+
return cast(Dict[K, V], self.__dict__["_data"]).items()
|
flwr/common/telemetry.py
CHANGED
|
@@ -132,53 +132,59 @@ class EventType(str, Enum):
|
|
|
132
132
|
# Ping
|
|
133
133
|
PING = auto()
|
|
134
134
|
|
|
135
|
-
#
|
|
135
|
+
# --- LEGACY FUNCTIONS -------------------------------------------------------------
|
|
136
|
+
|
|
137
|
+
# Legacy: `start_client` function
|
|
136
138
|
START_CLIENT_ENTER = auto()
|
|
137
139
|
START_CLIENT_LEAVE = auto()
|
|
138
140
|
|
|
139
|
-
#
|
|
141
|
+
# Legacy: `start_server` function
|
|
140
142
|
START_SERVER_ENTER = auto()
|
|
141
143
|
START_SERVER_LEAVE = auto()
|
|
142
144
|
|
|
143
|
-
#
|
|
144
|
-
|
|
145
|
-
|
|
145
|
+
# Legacy: `start_simulation` function
|
|
146
|
+
START_SIMULATION_ENTER = auto()
|
|
147
|
+
START_SIMULATION_LEAVE = auto()
|
|
146
148
|
|
|
147
|
-
#
|
|
148
|
-
RUN_FLEET_API_ENTER = auto()
|
|
149
|
-
RUN_FLEET_API_LEAVE = auto()
|
|
149
|
+
# --- `flwr` CLI -------------------------------------------------------------------
|
|
150
150
|
|
|
151
|
-
#
|
|
152
|
-
RUN_SUPERLINK_ENTER = auto()
|
|
153
|
-
RUN_SUPERLINK_LEAVE = auto()
|
|
151
|
+
# Not yet implemented
|
|
154
152
|
|
|
155
|
-
#
|
|
156
|
-
START_SIMULATION_ENTER = auto()
|
|
157
|
-
START_SIMULATION_LEAVE = auto()
|
|
153
|
+
# --- SuperExec --------------------------------------------------------------------
|
|
158
154
|
|
|
159
|
-
#
|
|
160
|
-
|
|
161
|
-
|
|
155
|
+
# SuperExec
|
|
156
|
+
RUN_SUPEREXEC_ENTER = auto()
|
|
157
|
+
RUN_SUPEREXEC_LEAVE = auto()
|
|
162
158
|
|
|
163
|
-
#
|
|
164
|
-
START_DRIVER_ENTER = auto()
|
|
165
|
-
START_DRIVER_LEAVE = auto()
|
|
159
|
+
# --- Simulation Engine ------------------------------------------------------------
|
|
166
160
|
|
|
167
|
-
# flower-
|
|
168
|
-
|
|
169
|
-
|
|
161
|
+
# CLI: flower-simulation
|
|
162
|
+
CLI_FLOWER_SIMULATION_ENTER = auto()
|
|
163
|
+
CLI_FLOWER_SIMULATION_LEAVE = auto()
|
|
170
164
|
|
|
171
|
-
#
|
|
172
|
-
|
|
173
|
-
|
|
165
|
+
# Python API: `run_simulation`
|
|
166
|
+
PYTHON_API_RUN_SIMULATION_ENTER = auto()
|
|
167
|
+
PYTHON_API_RUN_SIMULATION_LEAVE = auto()
|
|
174
168
|
|
|
175
|
-
#
|
|
169
|
+
# --- Deployment Engine ------------------------------------------------------------
|
|
170
|
+
|
|
171
|
+
# CLI: `flower-superlink`
|
|
172
|
+
RUN_SUPERLINK_ENTER = auto()
|
|
173
|
+
RUN_SUPERLINK_LEAVE = auto()
|
|
174
|
+
|
|
175
|
+
# CLI: `flower-supernode`
|
|
176
176
|
RUN_SUPERNODE_ENTER = auto()
|
|
177
177
|
RUN_SUPERNODE_LEAVE = auto()
|
|
178
178
|
|
|
179
|
-
#
|
|
180
|
-
|
|
181
|
-
|
|
179
|
+
# CLI: `flower-server-app`
|
|
180
|
+
RUN_SERVER_APP_ENTER = auto()
|
|
181
|
+
RUN_SERVER_APP_LEAVE = auto()
|
|
182
|
+
|
|
183
|
+
# --- DEPRECATED -------------------------------------------------------------------
|
|
184
|
+
|
|
185
|
+
# [DEPRECATED] CLI: `flower-client-app`
|
|
186
|
+
RUN_CLIENT_APP_ENTER = auto()
|
|
187
|
+
RUN_CLIENT_APP_LEAVE = auto()
|
|
182
188
|
|
|
183
189
|
|
|
184
190
|
# Use the ThreadPoolExecutor with max_workers=1 to have a queue
|
flwr/server/__init__.py
CHANGED
|
@@ -17,14 +17,12 @@
|
|
|
17
17
|
|
|
18
18
|
from . import strategy
|
|
19
19
|
from . import workflow as workflow
|
|
20
|
-
from .app import run_superlink as run_superlink
|
|
21
20
|
from .app import start_server as start_server
|
|
22
21
|
from .client_manager import ClientManager as ClientManager
|
|
23
22
|
from .client_manager import SimpleClientManager as SimpleClientManager
|
|
24
23
|
from .compat import LegacyContext as LegacyContext
|
|
25
24
|
from .driver import Driver as Driver
|
|
26
25
|
from .history import History as History
|
|
27
|
-
from .run_serverapp import run_server_app as run_server_app
|
|
28
26
|
from .server import Server as Server
|
|
29
27
|
from .server_app import ServerApp as ServerApp
|
|
30
28
|
from .server_config import ServerConfig as ServerConfig
|
|
@@ -40,8 +38,6 @@ __all__ = [
|
|
|
40
38
|
"ServerAppComponents",
|
|
41
39
|
"ServerConfig",
|
|
42
40
|
"SimpleClientManager",
|
|
43
|
-
"run_server_app",
|
|
44
|
-
"run_superlink",
|
|
45
41
|
"start_server",
|
|
46
42
|
"strategy",
|
|
47
43
|
"workflow",
|
flwr/server/app.py
CHANGED
|
@@ -36,6 +36,10 @@ from flwr.common import GRPC_MAX_MESSAGE_LENGTH, EventType, event
|
|
|
36
36
|
from flwr.common.address import parse_address
|
|
37
37
|
from flwr.common.config import get_flwr_dir
|
|
38
38
|
from flwr.common.constant import (
|
|
39
|
+
DRIVER_API_DEFAULT_ADDRESS,
|
|
40
|
+
FLEET_API_GRPC_BIDI_DEFAULT_ADDRESS,
|
|
41
|
+
FLEET_API_GRPC_RERE_DEFAULT_ADDRESS,
|
|
42
|
+
FLEET_API_REST_DEFAULT_ADDRESS,
|
|
39
43
|
MISSING_EXTRA_REST,
|
|
40
44
|
TRANSPORT_TYPE_GRPC_ADAPTER,
|
|
41
45
|
TRANSPORT_TYPE_GRPC_RERE,
|
|
@@ -68,18 +72,13 @@ from .superlink.fleet.grpc_rere.fleet_servicer import FleetServicer
|
|
|
68
72
|
from .superlink.fleet.grpc_rere.server_interceptor import AuthenticateServerInterceptor
|
|
69
73
|
from .superlink.state import StateFactory
|
|
70
74
|
|
|
71
|
-
ADDRESS_DRIVER_API = "0.0.0.0:9091"
|
|
72
|
-
ADDRESS_FLEET_API_GRPC_RERE = "0.0.0.0:9092"
|
|
73
|
-
ADDRESS_FLEET_API_GRPC_BIDI = "[::]:8080" # IPv6 to keep start_server compatible
|
|
74
|
-
ADDRESS_FLEET_API_REST = "0.0.0.0:9093"
|
|
75
|
-
|
|
76
75
|
DATABASE = ":flwr-in-memory-state:"
|
|
77
76
|
BASE_DIR = get_flwr_dir() / "superlink" / "ffs"
|
|
78
77
|
|
|
79
78
|
|
|
80
79
|
def start_server( # pylint: disable=too-many-arguments,too-many-locals
|
|
81
80
|
*,
|
|
82
|
-
server_address: str =
|
|
81
|
+
server_address: str = FLEET_API_GRPC_BIDI_DEFAULT_ADDRESS,
|
|
83
82
|
server: Optional[Server] = None,
|
|
84
83
|
config: Optional[ServerConfig] = None,
|
|
85
84
|
strategy: Optional[Strategy] = None,
|
|
@@ -232,9 +231,9 @@ def run_superlink() -> None:
|
|
|
232
231
|
TRANSPORT_TYPE_GRPC_RERE,
|
|
233
232
|
TRANSPORT_TYPE_GRPC_ADAPTER,
|
|
234
233
|
]:
|
|
235
|
-
args.fleet_api_address =
|
|
234
|
+
args.fleet_api_address = FLEET_API_GRPC_RERE_DEFAULT_ADDRESS
|
|
236
235
|
elif args.fleet_api_type == TRANSPORT_TYPE_REST:
|
|
237
|
-
args.fleet_api_address =
|
|
236
|
+
args.fleet_api_address = FLEET_API_REST_DEFAULT_ADDRESS
|
|
238
237
|
|
|
239
238
|
fleet_address, host, port = _format_address(args.fleet_api_address)
|
|
240
239
|
|
|
@@ -271,30 +270,31 @@ def run_superlink() -> None:
|
|
|
271
270
|
ssl_keyfile,
|
|
272
271
|
ssl_certfile,
|
|
273
272
|
state_factory,
|
|
273
|
+
ffs_factory,
|
|
274
274
|
num_workers,
|
|
275
275
|
),
|
|
276
276
|
)
|
|
277
277
|
fleet_thread.start()
|
|
278
278
|
bckg_threads.append(fleet_thread)
|
|
279
279
|
elif args.fleet_api_type == TRANSPORT_TYPE_GRPC_RERE:
|
|
280
|
-
maybe_keys =
|
|
280
|
+
maybe_keys = _try_setup_node_authentication(args, certificates)
|
|
281
281
|
interceptors: Optional[Sequence[grpc.ServerInterceptor]] = None
|
|
282
282
|
if maybe_keys is not None:
|
|
283
283
|
(
|
|
284
|
-
|
|
284
|
+
node_public_keys,
|
|
285
285
|
server_private_key,
|
|
286
286
|
server_public_key,
|
|
287
287
|
) = maybe_keys
|
|
288
288
|
state = state_factory.state()
|
|
289
|
-
state.
|
|
289
|
+
state.store_node_public_keys(node_public_keys)
|
|
290
290
|
state.store_server_private_public_key(
|
|
291
291
|
private_key_to_bytes(server_private_key),
|
|
292
292
|
public_key_to_bytes(server_public_key),
|
|
293
293
|
)
|
|
294
294
|
log(
|
|
295
295
|
INFO,
|
|
296
|
-
"
|
|
297
|
-
len(
|
|
296
|
+
"Node authentication enabled with %d known public keys",
|
|
297
|
+
len(node_public_keys),
|
|
298
298
|
)
|
|
299
299
|
interceptors = [AuthenticateServerInterceptor(state)]
|
|
300
300
|
|
|
@@ -310,6 +310,7 @@ def run_superlink() -> None:
|
|
|
310
310
|
fleet_server = _run_fleet_api_grpc_adapter(
|
|
311
311
|
address=fleet_address,
|
|
312
312
|
state_factory=state_factory,
|
|
313
|
+
ffs_factory=ffs_factory,
|
|
313
314
|
certificates=certificates,
|
|
314
315
|
)
|
|
315
316
|
grpc_servers.append(fleet_server)
|
|
@@ -342,7 +343,7 @@ def _format_address(address: str) -> Tuple[str, str, int]:
|
|
|
342
343
|
return (f"[{host}]:{port}" if is_v6 else f"{host}:{port}", host, port)
|
|
343
344
|
|
|
344
345
|
|
|
345
|
-
def
|
|
346
|
+
def _try_setup_node_authentication(
|
|
346
347
|
args: argparse.Namespace,
|
|
347
348
|
certificates: Optional[Tuple[bytes, bytes, bytes]],
|
|
348
349
|
) -> Optional[Tuple[Set[bytes], ec.EllipticCurvePrivateKey, ec.EllipticCurvePublicKey]]:
|
|
@@ -371,16 +372,16 @@ def _try_setup_client_authentication(
|
|
|
371
372
|
"`--ssl-keyfile`, and `—-ssl-ca-certfile` and try again."
|
|
372
373
|
)
|
|
373
374
|
|
|
374
|
-
|
|
375
|
-
if not
|
|
375
|
+
node_keys_file_path = Path(args.auth_list_public_keys)
|
|
376
|
+
if not node_keys_file_path.exists():
|
|
376
377
|
sys.exit(
|
|
377
378
|
"The provided path to the known public keys CSV file does not exist: "
|
|
378
|
-
f"{
|
|
379
|
+
f"{node_keys_file_path}. "
|
|
379
380
|
"Please provide the CSV file path containing known public keys "
|
|
380
381
|
"to '--auth-list-public-keys'."
|
|
381
382
|
)
|
|
382
383
|
|
|
383
|
-
|
|
384
|
+
node_public_keys: Set[bytes] = set()
|
|
384
385
|
|
|
385
386
|
try:
|
|
386
387
|
ssh_private_key = load_ssh_private_key(
|
|
@@ -411,13 +412,13 @@ def _try_setup_client_authentication(
|
|
|
411
412
|
"path points to a valid public key file and try again."
|
|
412
413
|
)
|
|
413
414
|
|
|
414
|
-
with open(
|
|
415
|
+
with open(node_keys_file_path, newline="", encoding="utf-8") as csvfile:
|
|
415
416
|
reader = csv.reader(csvfile)
|
|
416
417
|
for row in reader:
|
|
417
418
|
for element in row:
|
|
418
419
|
public_key = load_ssh_public_key(element.encode())
|
|
419
420
|
if isinstance(public_key, ec.EllipticCurvePublicKey):
|
|
420
|
-
|
|
421
|
+
node_public_keys.add(public_key_to_bytes(public_key))
|
|
421
422
|
else:
|
|
422
423
|
sys.exit(
|
|
423
424
|
"Error: Unable to parse the public keys in the CSV "
|
|
@@ -425,7 +426,7 @@ def _try_setup_client_authentication(
|
|
|
425
426
|
"known SSH public keys files and try again."
|
|
426
427
|
)
|
|
427
428
|
return (
|
|
428
|
-
|
|
429
|
+
node_public_keys,
|
|
429
430
|
ssh_private_key,
|
|
430
431
|
ssh_public_key,
|
|
431
432
|
)
|
|
@@ -516,12 +517,14 @@ def _run_fleet_api_grpc_rere(
|
|
|
516
517
|
def _run_fleet_api_grpc_adapter(
|
|
517
518
|
address: str,
|
|
518
519
|
state_factory: StateFactory,
|
|
520
|
+
ffs_factory: FfsFactory,
|
|
519
521
|
certificates: Optional[Tuple[bytes, bytes, bytes]],
|
|
520
522
|
) -> grpc.Server:
|
|
521
523
|
"""Run Fleet API (GrpcAdapter)."""
|
|
522
524
|
# Create Fleet API gRPC server
|
|
523
525
|
fleet_servicer = GrpcAdapterServicer(
|
|
524
526
|
state_factory=state_factory,
|
|
527
|
+
ffs_factory=ffs_factory,
|
|
525
528
|
)
|
|
526
529
|
fleet_add_servicer_to_server_fn = add_GrpcAdapterServicer_to_server
|
|
527
530
|
fleet_grpc_server = generic_create_grpc_server(
|
|
@@ -544,6 +547,7 @@ def _run_fleet_api_rest(
|
|
|
544
547
|
ssl_keyfile: Optional[str],
|
|
545
548
|
ssl_certfile: Optional[str],
|
|
546
549
|
state_factory: StateFactory,
|
|
550
|
+
ffs_factory: FfsFactory,
|
|
547
551
|
num_workers: int,
|
|
548
552
|
) -> None:
|
|
549
553
|
"""Run Driver API (REST-based)."""
|
|
@@ -558,6 +562,7 @@ def _run_fleet_api_rest(
|
|
|
558
562
|
|
|
559
563
|
# See: https://www.starlette.io/applications/#accessing-the-app-instance
|
|
560
564
|
fast_api_app.state.STATE_FACTORY = state_factory
|
|
565
|
+
fast_api_app.state.FFS_FACTORY = ffs_factory
|
|
561
566
|
|
|
562
567
|
uvicorn.run(
|
|
563
568
|
app="flwr.server.superlink.fleet.rest_rere.rest_api:app",
|
|
@@ -647,7 +652,7 @@ def _add_args_driver_api(parser: argparse.ArgumentParser) -> None:
|
|
|
647
652
|
parser.add_argument(
|
|
648
653
|
"--driver-api-address",
|
|
649
654
|
help="Driver API (gRPC) server address (IPv4, IPv6, or a domain name).",
|
|
650
|
-
default=
|
|
655
|
+
default=DRIVER_API_DEFAULT_ADDRESS,
|
|
651
656
|
)
|
|
652
657
|
|
|
653
658
|
|
flwr/server/compat/app.py
CHANGED
|
@@ -18,7 +18,6 @@
|
|
|
18
18
|
from logging import INFO
|
|
19
19
|
from typing import Optional
|
|
20
20
|
|
|
21
|
-
from flwr.common import EventType, event
|
|
22
21
|
from flwr.common.logger import log
|
|
23
22
|
from flwr.server.client_manager import ClientManager
|
|
24
23
|
from flwr.server.history import History
|
|
@@ -65,8 +64,6 @@ def start_driver( # pylint: disable=too-many-arguments, too-many-locals
|
|
|
65
64
|
hist : flwr.server.history.History
|
|
66
65
|
Object containing training and evaluation metrics.
|
|
67
66
|
"""
|
|
68
|
-
event(EventType.START_DRIVER_ENTER)
|
|
69
|
-
|
|
70
67
|
# Initialize the Driver API server and config
|
|
71
68
|
initialized_server, initialized_config = init_defaults(
|
|
72
69
|
server=server,
|
|
@@ -96,6 +93,4 @@ def start_driver( # pylint: disable=too-many-arguments, too-many-locals
|
|
|
96
93
|
f_stop.set()
|
|
97
94
|
thread.join()
|
|
98
95
|
|
|
99
|
-
event(EventType.START_SERVER_LEAVE)
|
|
100
|
-
|
|
101
96
|
return hist
|
|
@@ -21,7 +21,8 @@ from typing import Iterable, List, Optional, cast
|
|
|
21
21
|
|
|
22
22
|
import grpc
|
|
23
23
|
|
|
24
|
-
from flwr.common import DEFAULT_TTL,
|
|
24
|
+
from flwr.common import DEFAULT_TTL, Message, Metadata, RecordSet
|
|
25
|
+
from flwr.common.constant import DRIVER_API_DEFAULT_ADDRESS
|
|
25
26
|
from flwr.common.grpc import create_channel
|
|
26
27
|
from flwr.common.logger import log
|
|
27
28
|
from flwr.common.serde import (
|
|
@@ -45,8 +46,6 @@ from flwr.proto.task_pb2 import TaskIns # pylint: disable=E0611
|
|
|
45
46
|
|
|
46
47
|
from .driver import Driver
|
|
47
48
|
|
|
48
|
-
DEFAULT_SERVER_ADDRESS_DRIVER = "[::]:9091"
|
|
49
|
-
|
|
50
49
|
ERROR_MESSAGE_DRIVER_NOT_CONNECTED = """
|
|
51
50
|
[Driver] Error: Not connected.
|
|
52
51
|
|
|
@@ -73,7 +72,7 @@ class GrpcDriver(Driver):
|
|
|
73
72
|
def __init__( # pylint: disable=too-many-arguments
|
|
74
73
|
self,
|
|
75
74
|
run_id: int,
|
|
76
|
-
driver_service_address: str =
|
|
75
|
+
driver_service_address: str = DRIVER_API_DEFAULT_ADDRESS,
|
|
77
76
|
root_certificates: Optional[bytes] = None,
|
|
78
77
|
) -> None:
|
|
79
78
|
self._run_id = run_id
|
|
@@ -94,7 +93,6 @@ class GrpcDriver(Driver):
|
|
|
94
93
|
|
|
95
94
|
This will not call GetRun.
|
|
96
95
|
"""
|
|
97
|
-
event(EventType.DRIVER_CONNECT)
|
|
98
96
|
if self._is_connected:
|
|
99
97
|
log(WARNING, "Already connected")
|
|
100
98
|
return
|
|
@@ -108,7 +106,6 @@ class GrpcDriver(Driver):
|
|
|
108
106
|
|
|
109
107
|
def _disconnect(self) -> None:
|
|
110
108
|
"""Disconnect from the Driver API."""
|
|
111
|
-
event(EventType.DRIVER_DISCONNECT)
|
|
112
109
|
if not self._is_connected:
|
|
113
110
|
log(DEBUG, "Already disconnected")
|
|
114
111
|
return
|
flwr/server/run_serverapp.py
CHANGED
|
@@ -31,6 +31,7 @@ from flwr.common.config import (
|
|
|
31
31
|
get_project_config,
|
|
32
32
|
get_project_dir,
|
|
33
33
|
)
|
|
34
|
+
from flwr.common.constant import DRIVER_API_DEFAULT_ADDRESS
|
|
34
35
|
from flwr.common.logger import log, update_console_handler, warn_deprecated_feature
|
|
35
36
|
from flwr.common.object_ref import load_app
|
|
36
37
|
from flwr.common.typing import UserConfig
|
|
@@ -44,8 +45,6 @@ from .driver import Driver
|
|
|
44
45
|
from .driver.grpc_driver import GrpcDriver
|
|
45
46
|
from .server_app import LoadServerAppError, ServerApp
|
|
46
47
|
|
|
47
|
-
ADDRESS_DRIVER_API = "0.0.0.0:9091"
|
|
48
|
-
|
|
49
48
|
|
|
50
49
|
def run(
|
|
51
50
|
driver: Driver,
|
|
@@ -97,11 +96,26 @@ def run_server_app() -> None:
|
|
|
97
96
|
|
|
98
97
|
args = _parse_args_run_server_app().parse_args()
|
|
99
98
|
|
|
100
|
-
if
|
|
99
|
+
# Check if the server app reference is passed.
|
|
100
|
+
# Since Flower 1.11, passing a reference is not allowed.
|
|
101
|
+
app_path: Optional[str] = args.app
|
|
102
|
+
# If the provided app_path doesn't exist, and contains a ":",
|
|
103
|
+
# it is likely to be a server app reference instead of a path.
|
|
104
|
+
if app_path is not None and not Path(app_path).exists() and ":" in app_path:
|
|
105
|
+
sys.exit(
|
|
106
|
+
"It appears you've passed a reference like `server:app`.\n\n"
|
|
107
|
+
"Note that since version `1.11.0`, `flower-server-app` no longer supports "
|
|
108
|
+
"passing a reference to a `ServerApp` attribute. Instead, you need to pass "
|
|
109
|
+
"the path to Flower app via the argument `--app`. This is the path to a "
|
|
110
|
+
"directory containing a `pyproject.toml`. You can create a valid Flower "
|
|
111
|
+
"app by executing `flwr new` and following the prompt."
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
if args.server != DRIVER_API_DEFAULT_ADDRESS:
|
|
101
115
|
warn = "Passing flag --server is deprecated. Use --superlink instead."
|
|
102
116
|
warn_deprecated_feature(warn)
|
|
103
117
|
|
|
104
|
-
if args.superlink !=
|
|
118
|
+
if args.superlink != DRIVER_API_DEFAULT_ADDRESS:
|
|
105
119
|
# if `--superlink` also passed, then
|
|
106
120
|
# warn user that this argument overrides what was passed with `--server`
|
|
107
121
|
log(
|
|
@@ -151,7 +165,6 @@ def run_server_app() -> None:
|
|
|
151
165
|
cert_path,
|
|
152
166
|
)
|
|
153
167
|
|
|
154
|
-
app_path: Optional[str] = args.app
|
|
155
168
|
if not (app_path is None) ^ (args.run_id is None):
|
|
156
169
|
raise sys.exit(
|
|
157
170
|
"Please provide either a Flower App path or a Run ID, but not both. "
|
|
@@ -261,12 +274,12 @@ def _parse_args_run_server_app() -> argparse.ArgumentParser:
|
|
|
261
274
|
)
|
|
262
275
|
parser.add_argument(
|
|
263
276
|
"--server",
|
|
264
|
-
default=
|
|
277
|
+
default=DRIVER_API_DEFAULT_ADDRESS,
|
|
265
278
|
help="Server address",
|
|
266
279
|
)
|
|
267
280
|
parser.add_argument(
|
|
268
281
|
"--superlink",
|
|
269
|
-
default=
|
|
282
|
+
default=DRIVER_API_DEFAULT_ADDRESS,
|
|
270
283
|
help="SuperLink Driver API (gRPC-rere) address (IPv4, IPv6, or a domain name)",
|
|
271
284
|
)
|
|
272
285
|
parser.add_argument(
|