flwr-nightly 1.14.0.dev20241214__py3-none-any.whl → 1.15.0.dev20250107__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.
- flwr/cli/log.py +9 -7
- flwr/cli/login/login.py +1 -3
- flwr/cli/ls.py +25 -22
- flwr/cli/new/templates/app/.gitignore.tpl +3 -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 +3 -3
- 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 +11 -18
- flwr/cli/stop.py +71 -32
- flwr/cli/utils.py +81 -25
- flwr/client/app.py +11 -1
- flwr/client/client.py +0 -32
- flwr/client/clientapp/app.py +3 -1
- flwr/client/grpc_rere_client/connection.py +10 -4
- flwr/client/message_handler/message_handler.py +0 -2
- flwr/client/numpy_client.py +0 -44
- flwr/client/supernode/app.py +1 -2
- flwr/common/logger.py +16 -1
- flwr/common/record/recordset.py +1 -1
- flwr/common/retry_invoker.py +3 -1
- flwr/common/secure_aggregation/crypto/symmetric_encryption.py +45 -0
- flwr/common/telemetry.py +13 -3
- flwr/server/app.py +8 -8
- flwr/server/run_serverapp.py +8 -9
- flwr/server/serverapp/app.py +17 -2
- flwr/server/superlink/driver/serverappio_servicer.py +9 -0
- flwr/server/superlink/fleet/message_handler/message_handler.py +1 -3
- flwr/server/superlink/fleet/vce/vce_api.py +2 -2
- flwr/server/superlink/linkstate/in_memory_linkstate.py +10 -2
- flwr/server/superlink/linkstate/linkstate.py +4 -0
- flwr/server/superlink/linkstate/sqlite_linkstate.py +6 -2
- flwr/server/superlink/simulation/simulationio_servicer.py +13 -0
- flwr/simulation/app.py +15 -4
- flwr/simulation/run_simulation.py +35 -22
- flwr/simulation/simulationio_connection.py +3 -0
- {flwr_nightly-1.14.0.dev20241214.dist-info → flwr_nightly-1.15.0.dev20250107.dist-info}/METADATA +2 -2
- {flwr_nightly-1.14.0.dev20241214.dist-info → flwr_nightly-1.15.0.dev20250107.dist-info}/RECORD +46 -46
- {flwr_nightly-1.14.0.dev20241214.dist-info → flwr_nightly-1.15.0.dev20250107.dist-info}/LICENSE +0 -0
- {flwr_nightly-1.14.0.dev20241214.dist-info → flwr_nightly-1.15.0.dev20250107.dist-info}/WHEEL +0 -0
- {flwr_nightly-1.14.0.dev20241214.dist-info → flwr_nightly-1.15.0.dev20250107.dist-info}/entry_points.txt +0 -0
flwr/client/client.py
CHANGED
@@ -22,7 +22,6 @@ from abc import ABC
|
|
22
22
|
|
23
23
|
from flwr.common import (
|
24
24
|
Code,
|
25
|
-
Context,
|
26
25
|
EvaluateIns,
|
27
26
|
EvaluateRes,
|
28
27
|
FitIns,
|
@@ -34,14 +33,11 @@ from flwr.common import (
|
|
34
33
|
Parameters,
|
35
34
|
Status,
|
36
35
|
)
|
37
|
-
from flwr.common.logger import warn_deprecated_feature_with_example
|
38
36
|
|
39
37
|
|
40
38
|
class Client(ABC):
|
41
39
|
"""Abstract base class for Flower clients."""
|
42
40
|
|
43
|
-
_context: Context
|
44
|
-
|
45
41
|
def get_properties(self, ins: GetPropertiesIns) -> GetPropertiesRes:
|
46
42
|
"""Return set of client's properties.
|
47
43
|
|
@@ -143,34 +139,6 @@ class Client(ABC):
|
|
143
139
|
metrics={},
|
144
140
|
)
|
145
141
|
|
146
|
-
@property
|
147
|
-
def context(self) -> Context:
|
148
|
-
"""Getter for `Context` client attribute."""
|
149
|
-
warn_deprecated_feature_with_example(
|
150
|
-
"Accessing the context via the client's attribute is deprecated.",
|
151
|
-
example_message="Instead, pass it to the client's "
|
152
|
-
"constructor in your `client_fn()` which already "
|
153
|
-
"receives a context object.",
|
154
|
-
code_example="def client_fn(context: Context) -> Client:\n\n"
|
155
|
-
"\t\t# Your existing client_fn\n\n"
|
156
|
-
"\t\t# Pass `context` to the constructor\n"
|
157
|
-
"\t\treturn FlowerClient(context).to_client()",
|
158
|
-
)
|
159
|
-
return self._context
|
160
|
-
|
161
|
-
@context.setter
|
162
|
-
def context(self, context: Context) -> None:
|
163
|
-
"""Setter for `Context` client attribute."""
|
164
|
-
self._context = context
|
165
|
-
|
166
|
-
def get_context(self) -> Context:
|
167
|
-
"""Get the run context from this client."""
|
168
|
-
return self.context
|
169
|
-
|
170
|
-
def set_context(self, context: Context) -> None:
|
171
|
-
"""Apply a run context to this client."""
|
172
|
-
self.context = context
|
173
|
-
|
174
142
|
def to_client(self) -> Client:
|
175
143
|
"""Return client (itself)."""
|
176
144
|
return self
|
flwr/client/clientapp/app.py
CHANGED
@@ -32,6 +32,7 @@ from flwr.common.constant import CLIENTAPPIO_API_DEFAULT_CLIENT_ADDRESS, ErrorCo
|
|
32
32
|
from flwr.common.grpc import create_channel
|
33
33
|
from flwr.common.logger import log
|
34
34
|
from flwr.common.message import Error
|
35
|
+
from flwr.common.retry_invoker import _make_simple_grpc_retry_invoker, _wrap_stub
|
35
36
|
from flwr.common.serde import (
|
36
37
|
context_from_proto,
|
37
38
|
context_to_proto,
|
@@ -106,9 +107,9 @@ def run_clientapp( # pylint: disable=R0914
|
|
106
107
|
|
107
108
|
# Resolve directory where FABs are installed
|
108
109
|
flwr_dir_ = get_flwr_dir(flwr_dir)
|
109
|
-
|
110
110
|
try:
|
111
111
|
stub = ClientAppIoStub(channel)
|
112
|
+
_wrap_stub(stub, _make_simple_grpc_retry_invoker())
|
112
113
|
|
113
114
|
while True:
|
114
115
|
# If token is not set, loop until token is received from SuperNode
|
@@ -139,6 +140,7 @@ def run_clientapp( # pylint: disable=R0914
|
|
139
140
|
|
140
141
|
# Execute ClientApp
|
141
142
|
reply_message = client_app(message=message, context=context)
|
143
|
+
|
142
144
|
except Exception as ex: # pylint: disable=broad-exception-caught
|
143
145
|
# Don't update/change NodeState
|
144
146
|
|
@@ -42,7 +42,7 @@ from flwr.common.logger import log
|
|
42
42
|
from flwr.common.message import Message, Metadata
|
43
43
|
from flwr.common.retry_invoker import RetryInvoker
|
44
44
|
from flwr.common.serde import message_from_taskins, message_to_taskres, run_from_proto
|
45
|
-
from flwr.common.typing import Fab, Run
|
45
|
+
from flwr.common.typing import Fab, Run, RunNotRunningException
|
46
46
|
from flwr.proto.fab_pb2 import GetFabRequest, GetFabResponse # pylint: disable=E0611
|
47
47
|
from flwr.proto.fleet_pb2 import ( # pylint: disable=E0611
|
48
48
|
CreateNodeRequest,
|
@@ -155,10 +155,16 @@ def grpc_request_response( # pylint: disable=R0913,R0914,R0915,R0917
|
|
155
155
|
ping_thread: Optional[threading.Thread] = None
|
156
156
|
ping_stop_event = threading.Event()
|
157
157
|
|
158
|
+
def _should_giveup_fn(e: Exception) -> bool:
|
159
|
+
if e.code() == grpc.StatusCode.PERMISSION_DENIED: # type: ignore
|
160
|
+
raise RunNotRunningException
|
161
|
+
if e.code() == grpc.StatusCode.UNAVAILABLE: # type: ignore
|
162
|
+
return False
|
163
|
+
return True
|
164
|
+
|
158
165
|
# Restrict retries to cases where the status code is UNAVAILABLE
|
159
|
-
|
160
|
-
|
161
|
-
)
|
166
|
+
# If the status code is PERMISSION_DENIED, additionally raise RunNotRunningException
|
167
|
+
retry_invoker.should_giveup = _should_giveup_fn
|
162
168
|
|
163
169
|
###########################################################################
|
164
170
|
# ping/create_node/delete_node/receive/send/get_run functions
|
flwr/client/numpy_client.py
CHANGED
@@ -21,13 +21,11 @@ from typing import Callable
|
|
21
21
|
from flwr.client.client import Client
|
22
22
|
from flwr.common import (
|
23
23
|
Config,
|
24
|
-
Context,
|
25
24
|
NDArrays,
|
26
25
|
Scalar,
|
27
26
|
ndarrays_to_parameters,
|
28
27
|
parameters_to_ndarrays,
|
29
28
|
)
|
30
|
-
from flwr.common.logger import warn_deprecated_feature_with_example
|
31
29
|
from flwr.common.typing import (
|
32
30
|
Code,
|
33
31
|
EvaluateIns,
|
@@ -71,8 +69,6 @@ Example
|
|
71
69
|
class NumPyClient(ABC):
|
72
70
|
"""Abstract base class for Flower clients using NumPy."""
|
73
71
|
|
74
|
-
_context: Context
|
75
|
-
|
76
72
|
def get_properties(self, config: Config) -> dict[str, Scalar]:
|
77
73
|
"""Return a client's set of properties.
|
78
74
|
|
@@ -175,34 +171,6 @@ class NumPyClient(ABC):
|
|
175
171
|
_ = (self, parameters, config)
|
176
172
|
return 0.0, 0, {}
|
177
173
|
|
178
|
-
@property
|
179
|
-
def context(self) -> Context:
|
180
|
-
"""Getter for `Context` client attribute."""
|
181
|
-
warn_deprecated_feature_with_example(
|
182
|
-
"Accessing the context via the client's attribute is deprecated.",
|
183
|
-
example_message="Instead, pass it to the client's "
|
184
|
-
"constructor in your `client_fn()` which already "
|
185
|
-
"receives a context object.",
|
186
|
-
code_example="def client_fn(context: Context) -> Client:\n\n"
|
187
|
-
"\t\t# Your existing client_fn\n\n"
|
188
|
-
"\t\t# Pass `context` to the constructor\n"
|
189
|
-
"\t\treturn FlowerClient(context).to_client()",
|
190
|
-
)
|
191
|
-
return self._context
|
192
|
-
|
193
|
-
@context.setter
|
194
|
-
def context(self, context: Context) -> None:
|
195
|
-
"""Setter for `Context` client attribute."""
|
196
|
-
self._context = context
|
197
|
-
|
198
|
-
def get_context(self) -> Context:
|
199
|
-
"""Get the run context from this client."""
|
200
|
-
return self.context
|
201
|
-
|
202
|
-
def set_context(self, context: Context) -> None:
|
203
|
-
"""Apply a run context to this client."""
|
204
|
-
self.context = context
|
205
|
-
|
206
174
|
def to_client(self) -> Client:
|
207
175
|
"""Convert to object to Client type and return it."""
|
208
176
|
return _wrap_numpy_client(client=self)
|
@@ -299,21 +267,9 @@ def _evaluate(self: Client, ins: EvaluateIns) -> EvaluateRes:
|
|
299
267
|
)
|
300
268
|
|
301
269
|
|
302
|
-
def _get_context(self: Client) -> Context:
|
303
|
-
"""Return context of underlying NumPyClient."""
|
304
|
-
return self.numpy_client.get_context() # type: ignore
|
305
|
-
|
306
|
-
|
307
|
-
def _set_context(self: Client, context: Context) -> None:
|
308
|
-
"""Apply context to underlying NumPyClient."""
|
309
|
-
self.numpy_client.set_context(context) # type: ignore
|
310
|
-
|
311
|
-
|
312
270
|
def _wrap_numpy_client(client: NumPyClient) -> Client:
|
313
271
|
member_dict: dict[str, Callable] = { # type: ignore
|
314
272
|
"__init__": _constructor,
|
315
|
-
"get_context": _get_context,
|
316
|
-
"set_context": _set_context,
|
317
273
|
}
|
318
274
|
|
319
275
|
# Add wrapper type methods (if overridden)
|
flwr/client/supernode/app.py
CHANGED
@@ -114,9 +114,8 @@ def run_client_app() -> None:
|
|
114
114
|
event(EventType.RUN_CLIENT_APP_ENTER)
|
115
115
|
log(
|
116
116
|
ERROR,
|
117
|
-
"The command `flower-client-app` has been replaced by `
|
117
|
+
"The command `flower-client-app` has been replaced by `flwr run`.",
|
118
118
|
)
|
119
|
-
log(INFO, "Execute `flower-supernode --help` to learn how to use it.")
|
120
119
|
register_exit_handlers(event_type=EventType.RUN_CLIENT_APP_LEAVE)
|
121
120
|
|
122
121
|
|
flwr/common/logger.py
CHANGED
@@ -15,6 +15,7 @@
|
|
15
15
|
"""Flower Logger."""
|
16
16
|
|
17
17
|
|
18
|
+
import json as _json
|
18
19
|
import logging
|
19
20
|
import re
|
20
21
|
import sys
|
@@ -27,6 +28,8 @@ from queue import Empty, Queue
|
|
27
28
|
from typing import TYPE_CHECKING, Any, Optional, TextIO, Union
|
28
29
|
|
29
30
|
import grpc
|
31
|
+
import typer
|
32
|
+
from rich.console import Console
|
30
33
|
|
31
34
|
from flwr.proto.log_pb2 import PushLogsRequest # pylint: disable=E0611
|
32
35
|
from flwr.proto.node_pb2 import Node # pylint: disable=E0611
|
@@ -377,7 +380,7 @@ def stop_log_uploader(
|
|
377
380
|
log_uploader.join()
|
378
381
|
|
379
382
|
|
380
|
-
def
|
383
|
+
def _remove_emojis(text: str) -> str:
|
381
384
|
"""Remove emojis from the provided text."""
|
382
385
|
emoji_pattern = re.compile(
|
383
386
|
"["
|
@@ -391,3 +394,15 @@ def remove_emojis(text: str) -> str:
|
|
391
394
|
flags=re.UNICODE,
|
392
395
|
)
|
393
396
|
return emoji_pattern.sub(r"", text)
|
397
|
+
|
398
|
+
|
399
|
+
def print_json_error(msg: str, e: Union[typer.Exit, Exception]) -> None:
|
400
|
+
"""Print error message as JSON."""
|
401
|
+
Console().print_json(
|
402
|
+
_json.dumps(
|
403
|
+
{
|
404
|
+
"success": False,
|
405
|
+
"error-message": _remove_emojis(str(msg) + "\n" + str(e)),
|
406
|
+
}
|
407
|
+
)
|
408
|
+
)
|
flwr/common/record/recordset.py
CHANGED
@@ -151,7 +151,7 @@ class RecordSet:
|
|
151
151
|
>>> p_record = ParametersRecord({"my_array": arr})
|
152
152
|
>>>
|
153
153
|
>>> # Adding it to the record_set would look like this
|
154
|
-
>>> my_recordset.
|
154
|
+
>>> my_recordset.parameters_records["my_parameters"] = p_record
|
155
155
|
|
156
156
|
For additional examples on how to construct each of the records types shown
|
157
157
|
above, please refer to the documentation for :code:`ConfigsRecord`,
|
flwr/common/retry_invoker.py
CHANGED
@@ -30,6 +30,7 @@ from flwr.common.logger import log
|
|
30
30
|
from flwr.common.typing import RunNotRunningException
|
31
31
|
from flwr.proto.clientappio_pb2_grpc import ClientAppIoStub
|
32
32
|
from flwr.proto.serverappio_pb2_grpc import ServerAppIoStub
|
33
|
+
from flwr.proto.simulationio_pb2_grpc import SimulationIoStub
|
33
34
|
|
34
35
|
|
35
36
|
def exponential(
|
@@ -365,7 +366,8 @@ def _make_simple_grpc_retry_invoker() -> RetryInvoker:
|
|
365
366
|
|
366
367
|
|
367
368
|
def _wrap_stub(
|
368
|
-
stub: Union[ServerAppIoStub, ClientAppIoStub],
|
369
|
+
stub: Union[ServerAppIoStub, ClientAppIoStub, SimulationIoStub],
|
370
|
+
retry_invoker: RetryInvoker,
|
369
371
|
) -> None:
|
370
372
|
"""Wrap a gRPC stub with a retry invoker."""
|
371
373
|
|
@@ -117,3 +117,48 @@ def verify_hmac(key: bytes, message: bytes, hmac_value: bytes) -> bool:
|
|
117
117
|
return True
|
118
118
|
except InvalidSignature:
|
119
119
|
return False
|
120
|
+
|
121
|
+
|
122
|
+
def sign_message(private_key: ec.EllipticCurvePrivateKey, message: bytes) -> bytes:
|
123
|
+
"""Sign a message using the provided EC private key.
|
124
|
+
|
125
|
+
Parameters
|
126
|
+
----------
|
127
|
+
private_key : ec.EllipticCurvePrivateKey
|
128
|
+
The EC private key to sign the message with.
|
129
|
+
message : bytes
|
130
|
+
The message to be signed.
|
131
|
+
|
132
|
+
Returns
|
133
|
+
-------
|
134
|
+
bytes
|
135
|
+
The signature of the message.
|
136
|
+
"""
|
137
|
+
signature = private_key.sign(message, ec.ECDSA(hashes.SHA256()))
|
138
|
+
return signature
|
139
|
+
|
140
|
+
|
141
|
+
def verify_signature(
|
142
|
+
public_key: ec.EllipticCurvePublicKey, message: bytes, signature: bytes
|
143
|
+
) -> bool:
|
144
|
+
"""Verify a signature against a message using the provided EC public key.
|
145
|
+
|
146
|
+
Parameters
|
147
|
+
----------
|
148
|
+
public_key : ec.EllipticCurvePublicKey
|
149
|
+
The EC public key to verify the signature.
|
150
|
+
message : bytes
|
151
|
+
The original message.
|
152
|
+
signature : bytes
|
153
|
+
The signature to verify.
|
154
|
+
|
155
|
+
Returns
|
156
|
+
-------
|
157
|
+
bool
|
158
|
+
True if the signature is valid, False otherwise.
|
159
|
+
"""
|
160
|
+
try:
|
161
|
+
public_key.verify(signature, message, ec.ECDSA(hashes.SHA256()))
|
162
|
+
return True
|
163
|
+
except InvalidSignature:
|
164
|
+
return False
|
flwr/common/telemetry.py
CHANGED
@@ -151,6 +151,16 @@ class EventType(str, Enum):
|
|
151
151
|
|
152
152
|
# Not yet implemented
|
153
153
|
|
154
|
+
# --- `flwr-*` commands ------------------------------------------------------------
|
155
|
+
|
156
|
+
# CLI: flwr-simulation
|
157
|
+
FLWR_SIMULATION_RUN_ENTER = auto()
|
158
|
+
FLWR_SIMULATION_RUN_LEAVE = auto()
|
159
|
+
|
160
|
+
# CLI: flwr-serverapp
|
161
|
+
FLWR_SERVERAPP_RUN_ENTER = auto()
|
162
|
+
FLWR_SERVERAPP_RUN_LEAVE = auto()
|
163
|
+
|
154
164
|
# --- Simulation Engine ------------------------------------------------------------
|
155
165
|
|
156
166
|
# CLI: flower-simulation
|
@@ -171,12 +181,12 @@ class EventType(str, Enum):
|
|
171
181
|
RUN_SUPERNODE_ENTER = auto()
|
172
182
|
RUN_SUPERNODE_LEAVE = auto()
|
173
183
|
|
174
|
-
#
|
184
|
+
# --- DEPRECATED -------------------------------------------------------------------
|
185
|
+
|
186
|
+
# [DEPRECATED] CLI: `flower-server-app`
|
175
187
|
RUN_SERVER_APP_ENTER = auto()
|
176
188
|
RUN_SERVER_APP_LEAVE = auto()
|
177
189
|
|
178
|
-
# --- DEPRECATED -------------------------------------------------------------------
|
179
|
-
|
180
190
|
# [DEPRECATED] CLI: `flower-client-app`
|
181
191
|
RUN_CLIENT_APP_ENTER = auto()
|
182
192
|
RUN_CLIENT_APP_LEAVE = auto()
|
flwr/server/app.py
CHANGED
@@ -93,9 +93,13 @@ BASE_DIR = get_flwr_dir() / "superlink" / "ffs"
|
|
93
93
|
|
94
94
|
|
95
95
|
try:
|
96
|
-
from flwr.ee import get_exec_auth_plugins
|
96
|
+
from flwr.ee import add_ee_args_superlink, get_exec_auth_plugins
|
97
97
|
except ImportError:
|
98
98
|
|
99
|
+
# pylint: disable-next=unused-argument
|
100
|
+
def add_ee_args_superlink(parser: argparse.ArgumentParser) -> None:
|
101
|
+
"""Add EE-specific arguments to the parser."""
|
102
|
+
|
99
103
|
def get_exec_auth_plugins() -> dict[str, type[ExecAuthPlugin]]:
|
100
104
|
"""Return all Exec API authentication plugins."""
|
101
105
|
raise NotImplementedError("No authentication plugins are currently supported.")
|
@@ -370,6 +374,7 @@ def run_superlink() -> None:
|
|
370
374
|
server_public_key,
|
371
375
|
) = maybe_keys
|
372
376
|
state = state_factory.state()
|
377
|
+
state.clear_supernode_auth_keys_and_credentials()
|
373
378
|
state.store_node_public_keys(node_public_keys)
|
374
379
|
state.store_server_private_public_key(
|
375
380
|
private_key_to_bytes(server_private_key),
|
@@ -580,7 +585,7 @@ def _try_setup_node_authentication(
|
|
580
585
|
|
581
586
|
|
582
587
|
def _try_obtain_user_auth_config(args: argparse.Namespace) -> Optional[dict[str, Any]]:
|
583
|
-
if args
|
588
|
+
if getattr(args, "user_auth_config", None) is not None:
|
584
589
|
with open(args.user_auth_config, encoding="utf-8") as file:
|
585
590
|
config: dict[str, Any] = yaml.safe_load(file)
|
586
591
|
return config
|
@@ -703,6 +708,7 @@ def _parse_args_run_superlink() -> argparse.ArgumentParser:
|
|
703
708
|
)
|
704
709
|
|
705
710
|
_add_args_common(parser=parser)
|
711
|
+
add_ee_args_superlink(parser=parser)
|
706
712
|
_add_args_serverappio_api(parser=parser)
|
707
713
|
_add_args_fleet_api(parser=parser)
|
708
714
|
_add_args_exec_api(parser=parser)
|
@@ -792,12 +798,6 @@ def _add_args_common(parser: argparse.ArgumentParser) -> None:
|
|
792
798
|
type=str,
|
793
799
|
help="The SuperLink's public key (as a path str) to enable authentication.",
|
794
800
|
)
|
795
|
-
parser.add_argument(
|
796
|
-
"--user-auth-config",
|
797
|
-
help="The path to the user authentication configuration YAML file.",
|
798
|
-
type=str,
|
799
|
-
default=None,
|
800
|
-
)
|
801
801
|
|
802
802
|
|
803
803
|
def _add_args_serverappio_api(parser: argparse.ArgumentParser) -> None:
|
flwr/server/run_serverapp.py
CHANGED
@@ -15,12 +15,12 @@
|
|
15
15
|
"""Run ServerApp."""
|
16
16
|
|
17
17
|
|
18
|
-
import sys
|
19
18
|
from logging import DEBUG, ERROR
|
20
19
|
from typing import Optional
|
21
20
|
|
22
|
-
from flwr.common import Context
|
23
|
-
from flwr.common.
|
21
|
+
from flwr.common import Context, EventType, event
|
22
|
+
from flwr.common.exit_handlers import register_exit_handlers
|
23
|
+
from flwr.common.logger import log
|
24
24
|
from flwr.common.object_ref import load_app
|
25
25
|
|
26
26
|
from .driver import Driver
|
@@ -66,12 +66,11 @@ def run(
|
|
66
66
|
return context
|
67
67
|
|
68
68
|
|
69
|
-
# pylint: disable-next=too-many-branches,too-many-statements,too-many-locals
|
70
69
|
def run_server_app() -> None:
|
71
70
|
"""Run Flower server app."""
|
72
|
-
|
73
|
-
|
74
|
-
|
71
|
+
event(EventType.RUN_SERVER_APP_ENTER)
|
72
|
+
log(
|
73
|
+
ERROR,
|
74
|
+
"The command `flower-server-app` has been replaced by `flwr run`.",
|
75
75
|
)
|
76
|
-
|
77
|
-
sys.exit()
|
76
|
+
register_exit_handlers(event_type=EventType.RUN_SERVER_APP_LEAVE)
|
flwr/server/serverapp/app.py
CHANGED
@@ -25,6 +25,7 @@ from typing import Optional
|
|
25
25
|
|
26
26
|
from flwr.cli.config_utils import get_fab_metadata
|
27
27
|
from flwr.cli.install import install_from_fab
|
28
|
+
from flwr.cli.utils import get_sha256_hash
|
28
29
|
from flwr.common.args import add_args_flwr_app_common
|
29
30
|
from flwr.common.config import (
|
30
31
|
get_flwr_dir,
|
@@ -51,6 +52,7 @@ from flwr.common.serde import (
|
|
51
52
|
run_from_proto,
|
52
53
|
run_status_to_proto,
|
53
54
|
)
|
55
|
+
from flwr.common.telemetry import EventType, event
|
54
56
|
from flwr.common.typing import RunNotRunningException, RunStatus
|
55
57
|
from flwr.proto.run_pb2 import UpdateRunStatusRequest # pylint: disable=E0611
|
56
58
|
from flwr.proto.serverappio_pb2 import ( # pylint: disable=E0611
|
@@ -113,7 +115,8 @@ def run_serverapp( # pylint: disable=R0914, disable=W0212, disable=R0915
|
|
113
115
|
# Resolve directory where FABs are installed
|
114
116
|
flwr_dir_ = get_flwr_dir(flwr_dir)
|
115
117
|
log_uploader = None
|
116
|
-
|
118
|
+
success = True
|
119
|
+
hash_run_id = None
|
117
120
|
while True:
|
118
121
|
|
119
122
|
try:
|
@@ -129,6 +132,8 @@ def run_serverapp( # pylint: disable=R0914, disable=W0212, disable=R0915
|
|
129
132
|
run = run_from_proto(res.run)
|
130
133
|
fab = fab_from_proto(res.fab)
|
131
134
|
|
135
|
+
hash_run_id = get_sha256_hash(run.run_id)
|
136
|
+
|
132
137
|
driver.set_run(run.run_id)
|
133
138
|
|
134
139
|
# Start log uploader for this run
|
@@ -171,6 +176,11 @@ def run_serverapp( # pylint: disable=R0914, disable=W0212, disable=R0915
|
|
171
176
|
UpdateRunStatusRequest(run_id=run.run_id, run_status=run_status_proto)
|
172
177
|
)
|
173
178
|
|
179
|
+
event(
|
180
|
+
EventType.FLWR_SERVERAPP_RUN_ENTER,
|
181
|
+
event_details={"run-id-hash": hash_run_id},
|
182
|
+
)
|
183
|
+
|
174
184
|
# Load and run the ServerApp with the Driver
|
175
185
|
updated_context = run_(
|
176
186
|
driver=driver,
|
@@ -187,17 +197,18 @@ def run_serverapp( # pylint: disable=R0914, disable=W0212, disable=R0915
|
|
187
197
|
_ = driver._stub.PushServerAppOutputs(out_req)
|
188
198
|
|
189
199
|
run_status = RunStatus(Status.FINISHED, SubStatus.COMPLETED, "")
|
190
|
-
|
191
200
|
except RunNotRunningException:
|
192
201
|
log(INFO, "")
|
193
202
|
log(INFO, "Run ID %s stopped.", run.run_id)
|
194
203
|
log(INFO, "")
|
195
204
|
run_status = None
|
205
|
+
success = False
|
196
206
|
|
197
207
|
except Exception as ex: # pylint: disable=broad-exception-caught
|
198
208
|
exc_entity = "ServerApp"
|
199
209
|
log(ERROR, "%s raised an exception", exc_entity, exc_info=ex)
|
200
210
|
run_status = RunStatus(Status.FINISHED, SubStatus.FAILED, str(ex))
|
211
|
+
success = False
|
201
212
|
|
202
213
|
finally:
|
203
214
|
# Stop log uploader for this run and upload final logs
|
@@ -213,6 +224,10 @@ def run_serverapp( # pylint: disable=R0914, disable=W0212, disable=R0915
|
|
213
224
|
run_id=run.run_id, run_status=run_status_proto
|
214
225
|
)
|
215
226
|
)
|
227
|
+
event(
|
228
|
+
EventType.FLWR_SERVERAPP_RUN_LEAVE,
|
229
|
+
event_details={"run-id-hash": hash_run_id, "success": success},
|
230
|
+
)
|
216
231
|
|
217
232
|
# Stop the loop if `flwr-serverapp` is expected to process a single run
|
218
233
|
if run_once:
|
@@ -159,6 +159,9 @@ class ServerAppIoServicer(serverappio_pb2_grpc.ServerAppIoServicer):
|
|
159
159
|
for task_ins in request.task_ins_list:
|
160
160
|
validation_errors = validate_task_ins_or_res(task_ins)
|
161
161
|
_raise_if(bool(validation_errors), ", ".join(validation_errors))
|
162
|
+
_raise_if(
|
163
|
+
request.run_id != task_ins.run_id, "`task_ins` has mismatched `run_id`"
|
164
|
+
)
|
162
165
|
|
163
166
|
# Store each TaskIns
|
164
167
|
task_ids: list[Optional[UUID]] = []
|
@@ -193,6 +196,12 @@ class ServerAppIoServicer(serverappio_pb2_grpc.ServerAppIoServicer):
|
|
193
196
|
# Read from state
|
194
197
|
task_res_list: list[TaskRes] = state.get_task_res(task_ids=task_ids)
|
195
198
|
|
199
|
+
# Validate request
|
200
|
+
for task_res in task_res_list:
|
201
|
+
_raise_if(
|
202
|
+
request.run_id != task_res.run_id, "`task_res` has mismatched `run_id`"
|
203
|
+
)
|
204
|
+
|
196
205
|
# Delete the TaskIns/TaskRes pairs if TaskRes is found
|
197
206
|
task_ins_ids_to_delete = {
|
198
207
|
UUID(task_res.task.ancestry[0]) for task_res in task_res_list
|
@@ -123,9 +123,7 @@ def push_task_res(request: PushTaskResRequest, state: LinkState) -> PushTaskResR
|
|
123
123
|
return response
|
124
124
|
|
125
125
|
|
126
|
-
def get_run(
|
127
|
-
request: GetRunRequest, state: LinkState # pylint: disable=W0613
|
128
|
-
) -> GetRunResponse:
|
126
|
+
def get_run(request: GetRunRequest, state: LinkState) -> GetRunResponse:
|
129
127
|
"""Get run information."""
|
130
128
|
run = state.get_run(request.run_id)
|
131
129
|
|
@@ -182,8 +182,8 @@ def run_api(
|
|
182
182
|
f_stop: threading.Event,
|
183
183
|
) -> None:
|
184
184
|
"""Run the VCE."""
|
185
|
-
taskins_queue:
|
186
|
-
taskres_queue:
|
185
|
+
taskins_queue: Queue[TaskIns] = Queue()
|
186
|
+
taskres_queue: Queue[TaskRes] = Queue()
|
187
187
|
|
188
188
|
try:
|
189
189
|
|
@@ -430,10 +430,17 @@ class InMemoryLinkState(LinkState): # pylint: disable=R0902,R0904
|
|
430
430
|
"""Retrieve `server_public_key` in urlsafe bytes."""
|
431
431
|
return self.server_public_key
|
432
432
|
|
433
|
+
def clear_supernode_auth_keys_and_credentials(self) -> None:
|
434
|
+
"""Clear stored `node_public_keys` and credentials in the link state if any."""
|
435
|
+
with self.lock:
|
436
|
+
self.server_private_key = None
|
437
|
+
self.server_public_key = None
|
438
|
+
self.node_public_keys.clear()
|
439
|
+
|
433
440
|
def store_node_public_keys(self, public_keys: set[bytes]) -> None:
|
434
441
|
"""Store a set of `node_public_keys` in the link state."""
|
435
442
|
with self.lock:
|
436
|
-
self.node_public_keys
|
443
|
+
self.node_public_keys.update(public_keys)
|
437
444
|
|
438
445
|
def store_node_public_key(self, public_key: bytes) -> None:
|
439
446
|
"""Store a `node_public_key` in the link state."""
|
@@ -442,7 +449,8 @@ class InMemoryLinkState(LinkState): # pylint: disable=R0902,R0904
|
|
442
449
|
|
443
450
|
def get_node_public_keys(self) -> set[bytes]:
|
444
451
|
"""Retrieve all currently stored `node_public_keys` as a set."""
|
445
|
-
|
452
|
+
with self.lock:
|
453
|
+
return self.node_public_keys.copy()
|
446
454
|
|
447
455
|
def get_run_ids(self) -> set[int]:
|
448
456
|
"""Retrieve all run IDs."""
|
@@ -284,6 +284,10 @@ class LinkState(abc.ABC): # pylint: disable=R0904
|
|
284
284
|
def get_server_public_key(self) -> Optional[bytes]:
|
285
285
|
"""Retrieve `server_public_key` in urlsafe bytes."""
|
286
286
|
|
287
|
+
@abc.abstractmethod
|
288
|
+
def clear_supernode_auth_keys_and_credentials(self) -> None:
|
289
|
+
"""Clear stored `node_public_keys` and credentials in the link state if any."""
|
290
|
+
|
287
291
|
@abc.abstractmethod
|
288
292
|
def store_node_public_keys(self, public_keys: set[bytes]) -> None:
|
289
293
|
"""Store a set of `node_public_keys` in the link state."""
|
@@ -761,8 +761,6 @@ class SqliteLinkState(LinkState): # pylint: disable=R0904
|
|
761
761
|
"federation_options, pending_at, starting_at, running_at, finished_at, "
|
762
762
|
"sub_status, details) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);"
|
763
763
|
)
|
764
|
-
if fab_hash:
|
765
|
-
fab_id, fab_version = "", ""
|
766
764
|
override_config_json = json.dumps(override_config)
|
767
765
|
data = [
|
768
766
|
sint64_run_id,
|
@@ -820,6 +818,12 @@ class SqliteLinkState(LinkState): # pylint: disable=R0904
|
|
820
818
|
public_key = None
|
821
819
|
return public_key
|
822
820
|
|
821
|
+
def clear_supernode_auth_keys_and_credentials(self) -> None:
|
822
|
+
"""Clear stored `node_public_keys` and credentials in the link state if any."""
|
823
|
+
queries = ["DELETE FROM public_key;", "DELETE FROM credential;"]
|
824
|
+
for query in queries:
|
825
|
+
self.query(query)
|
826
|
+
|
823
827
|
def store_node_public_keys(self, public_keys: set[bytes]) -> None:
|
824
828
|
"""Store a set of `node_public_keys` in the link state."""
|
825
829
|
query = "INSERT INTO public_key (public_key) VALUES (?)"
|