flwr 1.19.0__py3-none-any.whl → 1.21.0__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/__init__.py +4 -1
- flwr/app/__init__.py +28 -0
- flwr/app/exception.py +31 -0
- flwr/cli/auth_plugin/oidc_cli_plugin.py +4 -4
- flwr/cli/build.py +15 -5
- flwr/cli/cli_user_auth_interceptor.py +1 -1
- flwr/cli/config_utils.py +3 -3
- flwr/cli/constant.py +25 -8
- flwr/cli/log.py +9 -9
- flwr/cli/login/login.py +3 -3
- flwr/cli/ls.py +5 -5
- flwr/cli/new/new.py +23 -4
- flwr/cli/new/templates/app/README.flowertune.md.tpl +2 -0
- flwr/cli/new/templates/app/README.md.tpl +5 -0
- flwr/cli/new/templates/app/code/__init__.pytorch_msg_api.py.tpl +1 -0
- flwr/cli/new/templates/app/code/client.pytorch_msg_api.py.tpl +80 -0
- flwr/cli/new/templates/app/code/server.pytorch_msg_api.py.tpl +41 -0
- flwr/cli/new/templates/app/code/task.pytorch_msg_api.py.tpl +98 -0
- flwr/cli/new/templates/app/pyproject.baseline.toml.tpl +14 -3
- flwr/cli/new/templates/app/pyproject.flowertune.toml.tpl +13 -1
- flwr/cli/new/templates/app/pyproject.huggingface.toml.tpl +21 -2
- flwr/cli/new/templates/app/pyproject.jax.toml.tpl +18 -1
- flwr/cli/new/templates/app/pyproject.mlx.toml.tpl +19 -2
- flwr/cli/new/templates/app/pyproject.numpy.toml.tpl +18 -1
- flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl +20 -3
- flwr/cli/new/templates/app/pyproject.pytorch_msg_api.toml.tpl +53 -0
- flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl +18 -1
- flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl +18 -1
- flwr/cli/run/run.py +53 -50
- flwr/cli/stop.py +7 -4
- flwr/cli/utils.py +29 -11
- flwr/client/grpc_adapter_client/connection.py +11 -4
- flwr/client/grpc_rere_client/connection.py +93 -129
- flwr/client/rest_client/connection.py +134 -164
- flwr/clientapp/__init__.py +10 -0
- flwr/clientapp/mod/__init__.py +26 -0
- flwr/clientapp/mod/centraldp_mods.py +132 -0
- flwr/common/args.py +20 -6
- flwr/common/auth_plugin/__init__.py +4 -4
- flwr/common/auth_plugin/auth_plugin.py +7 -7
- flwr/common/constant.py +26 -5
- flwr/common/event_log_plugin/event_log_plugin.py +1 -1
- flwr/common/exit/__init__.py +4 -0
- flwr/common/exit/exit.py +8 -1
- flwr/common/exit/exit_code.py +42 -8
- flwr/common/exit/exit_handler.py +62 -0
- flwr/common/{exit_handlers.py → exit/signal_handler.py} +20 -37
- flwr/common/grpc.py +1 -1
- flwr/common/{inflatable_grpc_utils.py → inflatable_protobuf_utils.py} +52 -10
- flwr/common/inflatable_utils.py +191 -24
- flwr/common/logger.py +1 -1
- flwr/common/record/array.py +101 -22
- flwr/common/record/arraychunk.py +59 -0
- flwr/common/retry_invoker.py +30 -11
- flwr/common/serde.py +0 -28
- flwr/common/telemetry.py +4 -0
- flwr/compat/client/app.py +14 -31
- flwr/compat/server/app.py +2 -2
- flwr/proto/appio_pb2.py +51 -0
- flwr/proto/appio_pb2.pyi +195 -0
- flwr/proto/appio_pb2_grpc.py +4 -0
- flwr/proto/appio_pb2_grpc.pyi +4 -0
- flwr/proto/clientappio_pb2.py +4 -19
- flwr/proto/clientappio_pb2.pyi +0 -125
- flwr/proto/clientappio_pb2_grpc.py +269 -29
- flwr/proto/clientappio_pb2_grpc.pyi +114 -21
- flwr/proto/control_pb2.py +62 -0
- flwr/proto/{exec_pb2_grpc.py → control_pb2_grpc.py} +54 -54
- flwr/proto/{exec_pb2_grpc.pyi → control_pb2_grpc.pyi} +28 -28
- flwr/proto/fleet_pb2.py +12 -20
- flwr/proto/fleet_pb2.pyi +6 -36
- flwr/proto/serverappio_pb2.py +8 -31
- flwr/proto/serverappio_pb2.pyi +0 -152
- flwr/proto/serverappio_pb2_grpc.py +107 -38
- flwr/proto/serverappio_pb2_grpc.pyi +47 -20
- flwr/proto/simulationio_pb2.py +4 -11
- flwr/proto/simulationio_pb2.pyi +0 -58
- flwr/proto/simulationio_pb2_grpc.py +129 -27
- flwr/proto/simulationio_pb2_grpc.pyi +52 -13
- flwr/server/app.py +130 -153
- flwr/server/fleet_event_log_interceptor.py +4 -0
- flwr/server/grid/grpc_grid.py +94 -54
- flwr/server/grid/inmemory_grid.py +1 -0
- flwr/server/serverapp/app.py +165 -144
- flwr/server/superlink/fleet/grpc_adapter/grpc_adapter_servicer.py +8 -0
- flwr/server/superlink/fleet/grpc_rere/fleet_servicer.py +1 -1
- flwr/server/superlink/fleet/grpc_rere/server_interceptor.py +2 -5
- flwr/server/superlink/fleet/message_handler/message_handler.py +10 -16
- flwr/server/superlink/fleet/rest_rere/rest_api.py +1 -2
- flwr/server/superlink/fleet/vce/backend/raybackend.py +3 -1
- flwr/server/superlink/fleet/vce/vce_api.py +6 -6
- flwr/server/superlink/linkstate/in_memory_linkstate.py +34 -0
- flwr/server/superlink/linkstate/linkstate.py +2 -1
- flwr/server/superlink/linkstate/sqlite_linkstate.py +45 -0
- flwr/server/superlink/serverappio/serverappio_grpc.py +2 -2
- flwr/server/superlink/serverappio/serverappio_servicer.py +95 -48
- flwr/server/superlink/simulation/simulationio_grpc.py +1 -1
- flwr/server/superlink/simulation/simulationio_servicer.py +98 -22
- flwr/server/superlink/utils.py +0 -35
- flwr/serverapp/__init__.py +12 -0
- flwr/serverapp/dp_fixed_clipping.py +352 -0
- flwr/serverapp/exception.py +38 -0
- flwr/serverapp/strategy/__init__.py +38 -0
- flwr/serverapp/strategy/dp_fixed_clipping.py +352 -0
- flwr/serverapp/strategy/fedadagrad.py +162 -0
- flwr/serverapp/strategy/fedadam.py +181 -0
- flwr/serverapp/strategy/fedavg.py +295 -0
- flwr/serverapp/strategy/fedopt.py +218 -0
- flwr/serverapp/strategy/fedyogi.py +173 -0
- flwr/serverapp/strategy/result.py +105 -0
- flwr/serverapp/strategy/strategy.py +285 -0
- flwr/serverapp/strategy/strategy_utils.py +251 -0
- flwr/serverapp/strategy/strategy_utils_tests.py +304 -0
- flwr/simulation/app.py +159 -154
- flwr/simulation/run_simulation.py +17 -0
- flwr/supercore/app_utils.py +58 -0
- flwr/supercore/cli/__init__.py +22 -0
- flwr/supercore/cli/flower_superexec.py +141 -0
- flwr/supercore/corestate/__init__.py +22 -0
- flwr/supercore/corestate/corestate.py +81 -0
- flwr/{server/superlink → supercore}/ffs/disk_ffs.py +1 -1
- flwr/supercore/grpc_health/__init__.py +25 -0
- flwr/supercore/grpc_health/health_server.py +53 -0
- flwr/supercore/grpc_health/simple_health_servicer.py +38 -0
- flwr/supercore/license_plugin/__init__.py +22 -0
- flwr/supercore/license_plugin/license_plugin.py +26 -0
- flwr/supercore/object_store/in_memory_object_store.py +31 -31
- flwr/supercore/object_store/object_store.py +20 -42
- flwr/supercore/object_store/utils.py +43 -0
- flwr/{superexec → supercore/superexec}/__init__.py +1 -1
- flwr/supercore/superexec/plugin/__init__.py +28 -0
- flwr/supercore/superexec/plugin/base_exec_plugin.py +53 -0
- flwr/supercore/superexec/plugin/clientapp_exec_plugin.py +28 -0
- flwr/supercore/superexec/plugin/exec_plugin.py +71 -0
- flwr/supercore/superexec/plugin/serverapp_exec_plugin.py +28 -0
- flwr/supercore/superexec/plugin/simulation_exec_plugin.py +28 -0
- flwr/supercore/superexec/run_superexec.py +185 -0
- flwr/supercore/utils.py +32 -0
- flwr/superlink/servicer/__init__.py +15 -0
- flwr/superlink/servicer/control/__init__.py +22 -0
- flwr/{superexec/exec_event_log_interceptor.py → superlink/servicer/control/control_event_log_interceptor.py} +9 -5
- flwr/{superexec/exec_grpc.py → superlink/servicer/control/control_grpc.py} +39 -28
- flwr/superlink/servicer/control/control_license_interceptor.py +82 -0
- flwr/{superexec/exec_servicer.py → superlink/servicer/control/control_servicer.py} +79 -31
- flwr/{superexec/exec_user_auth_interceptor.py → superlink/servicer/control/control_user_auth_interceptor.py} +18 -10
- flwr/supernode/cli/flower_supernode.py +3 -7
- flwr/supernode/cli/flwr_clientapp.py +20 -16
- flwr/supernode/nodestate/in_memory_nodestate.py +13 -4
- flwr/supernode/nodestate/nodestate.py +3 -44
- flwr/supernode/runtime/run_clientapp.py +129 -115
- flwr/supernode/servicer/clientappio/__init__.py +1 -3
- flwr/supernode/servicer/clientappio/clientappio_servicer.py +217 -165
- flwr/supernode/start_client_internal.py +205 -148
- {flwr-1.19.0.dist-info → flwr-1.21.0.dist-info}/METADATA +5 -3
- {flwr-1.19.0.dist-info → flwr-1.21.0.dist-info}/RECORD +161 -117
- {flwr-1.19.0.dist-info → flwr-1.21.0.dist-info}/entry_points.txt +1 -0
- flwr/common/inflatable_rest_utils.py +0 -99
- flwr/proto/exec_pb2.py +0 -62
- flwr/superexec/app.py +0 -45
- flwr/superexec/deployment.py +0 -192
- flwr/superexec/executor.py +0 -100
- flwr/superexec/simulation.py +0 -130
- /flwr/proto/{exec_pb2.pyi → control_pb2.pyi} +0 -0
- /flwr/{server/superlink → supercore}/ffs/__init__.py +0 -0
- /flwr/{server/superlink → supercore}/ffs/ffs.py +0 -0
- /flwr/{server/superlink → supercore}/ffs/ffs_factory.py +0 -0
- {flwr-1.19.0.dist-info → flwr-1.21.0.dist-info}/WHEEL +0 -0
|
@@ -12,9 +12,10 @@
|
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
# ==============================================================================
|
|
15
|
-
"""
|
|
15
|
+
"""Control API servicer."""
|
|
16
16
|
|
|
17
17
|
|
|
18
|
+
import hashlib
|
|
18
19
|
import time
|
|
19
20
|
from collections.abc import Generator
|
|
20
21
|
from logging import ERROR, INFO
|
|
@@ -22,10 +23,13 @@ from typing import Any, Optional, cast
|
|
|
22
23
|
|
|
23
24
|
import grpc
|
|
24
25
|
|
|
25
|
-
from flwr.
|
|
26
|
-
from flwr.common
|
|
26
|
+
from flwr.cli.config_utils import get_fab_metadata
|
|
27
|
+
from flwr.common import Context, RecordDict, now
|
|
28
|
+
from flwr.common.auth_plugin import ControlAuthPlugin
|
|
27
29
|
from flwr.common.constant import (
|
|
30
|
+
FAB_MAX_SIZE,
|
|
28
31
|
LOG_STREAM_INTERVAL,
|
|
32
|
+
NO_USER_AUTH_MESSAGE,
|
|
29
33
|
RUN_ID_NOT_FOUND_MESSAGE,
|
|
30
34
|
Status,
|
|
31
35
|
SubStatus,
|
|
@@ -36,9 +40,9 @@ from flwr.common.serde import (
|
|
|
36
40
|
run_to_proto,
|
|
37
41
|
user_config_from_proto,
|
|
38
42
|
)
|
|
39
|
-
from flwr.common.typing import Run, RunStatus
|
|
40
|
-
from flwr.proto import
|
|
41
|
-
from flwr.proto.
|
|
43
|
+
from flwr.common.typing import Fab, Run, RunStatus
|
|
44
|
+
from flwr.proto import control_pb2_grpc # pylint: disable=E0611
|
|
45
|
+
from flwr.proto.control_pb2 import ( # pylint: disable=E0611
|
|
42
46
|
GetAuthTokensRequest,
|
|
43
47
|
GetAuthTokensResponse,
|
|
44
48
|
GetLoginDetailsRequest,
|
|
@@ -52,57 +56,101 @@ from flwr.proto.exec_pb2 import ( # pylint: disable=E0611
|
|
|
52
56
|
StreamLogsRequest,
|
|
53
57
|
StreamLogsResponse,
|
|
54
58
|
)
|
|
55
|
-
from flwr.server.superlink.ffs.ffs_factory import FfsFactory
|
|
56
59
|
from flwr.server.superlink.linkstate import LinkState, LinkStateFactory
|
|
60
|
+
from flwr.supercore.ffs import FfsFactory
|
|
57
61
|
from flwr.supercore.object_store import ObjectStore, ObjectStoreFactory
|
|
58
62
|
|
|
59
|
-
from .
|
|
60
|
-
from .executor import Executor
|
|
63
|
+
from .control_user_auth_interceptor import shared_account_info
|
|
61
64
|
|
|
62
65
|
|
|
63
|
-
class
|
|
64
|
-
"""
|
|
66
|
+
class ControlServicer(control_pb2_grpc.ControlServicer):
|
|
67
|
+
"""Control API servicer."""
|
|
65
68
|
|
|
66
69
|
def __init__( # pylint: disable=R0913, R0917
|
|
67
70
|
self,
|
|
68
71
|
linkstate_factory: LinkStateFactory,
|
|
69
72
|
ffs_factory: FfsFactory,
|
|
70
73
|
objectstore_factory: ObjectStoreFactory,
|
|
71
|
-
|
|
72
|
-
auth_plugin: Optional[
|
|
74
|
+
is_simulation: bool,
|
|
75
|
+
auth_plugin: Optional[ControlAuthPlugin] = None,
|
|
73
76
|
) -> None:
|
|
74
77
|
self.linkstate_factory = linkstate_factory
|
|
75
78
|
self.ffs_factory = ffs_factory
|
|
76
79
|
self.objectstore_factory = objectstore_factory
|
|
77
|
-
self.
|
|
78
|
-
self.executor.initialize(linkstate_factory, ffs_factory)
|
|
80
|
+
self.is_simulation = is_simulation
|
|
79
81
|
self.auth_plugin = auth_plugin
|
|
80
82
|
|
|
81
83
|
def StartRun(
|
|
82
84
|
self, request: StartRunRequest, context: grpc.ServicerContext
|
|
83
85
|
) -> StartRunResponse:
|
|
84
86
|
"""Create run ID."""
|
|
85
|
-
log(INFO, "
|
|
87
|
+
log(INFO, "ControlServicer.StartRun")
|
|
88
|
+
state = self.linkstate_factory.state()
|
|
89
|
+
ffs = self.ffs_factory.ffs()
|
|
90
|
+
|
|
91
|
+
if len(request.fab.content) > FAB_MAX_SIZE:
|
|
92
|
+
log(
|
|
93
|
+
ERROR,
|
|
94
|
+
"FAB size exceeds maximum allowed size of %d bytes.",
|
|
95
|
+
FAB_MAX_SIZE,
|
|
96
|
+
)
|
|
97
|
+
return StartRunResponse()
|
|
86
98
|
|
|
87
99
|
flwr_aid = shared_account_info.get().flwr_aid if self.auth_plugin else None
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
100
|
+
override_config = user_config_from_proto(request.override_config)
|
|
101
|
+
federation_options = config_record_from_proto(request.federation_options)
|
|
102
|
+
fab_file = request.fab.content
|
|
103
|
+
|
|
104
|
+
try:
|
|
105
|
+
# Check that num-supernodes is set
|
|
106
|
+
if self.is_simulation and "num-supernodes" not in federation_options:
|
|
107
|
+
raise ValueError(
|
|
108
|
+
"Federation options doesn't contain key `num-supernodes`."
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
# Create run
|
|
112
|
+
fab = Fab(hashlib.sha256(fab_file).hexdigest(), fab_file)
|
|
113
|
+
fab_hash = ffs.put(fab.content, {})
|
|
114
|
+
if fab_hash != fab.hash_str:
|
|
115
|
+
raise RuntimeError(
|
|
116
|
+
f"FAB ({fab.hash_str}) hash from request doesn't match contents"
|
|
117
|
+
)
|
|
118
|
+
fab_id, fab_version = get_fab_metadata(fab.content)
|
|
119
|
+
|
|
120
|
+
run_id = state.create_run(
|
|
121
|
+
fab_id,
|
|
122
|
+
fab_version,
|
|
123
|
+
fab_hash,
|
|
124
|
+
override_config,
|
|
125
|
+
federation_options,
|
|
126
|
+
flwr_aid,
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
# Create an empty context for the Run
|
|
130
|
+
context = Context(
|
|
131
|
+
run_id=run_id,
|
|
132
|
+
node_id=0,
|
|
133
|
+
node_config={},
|
|
134
|
+
state=RecordDict(),
|
|
135
|
+
run_config={},
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
# Register the context at the LinkState
|
|
139
|
+
state.set_serverapp_context(run_id=run_id, context=context)
|
|
94
140
|
|
|
95
|
-
|
|
96
|
-
|
|
141
|
+
# pylint: disable-next=broad-except
|
|
142
|
+
except Exception as e:
|
|
143
|
+
log(ERROR, "Could not start run: %s", str(e))
|
|
97
144
|
return StartRunResponse()
|
|
98
145
|
|
|
146
|
+
log(INFO, "Created run %s", str(run_id))
|
|
99
147
|
return StartRunResponse(run_id=run_id)
|
|
100
148
|
|
|
101
149
|
def StreamLogs( # pylint: disable=C0103
|
|
102
150
|
self, request: StreamLogsRequest, context: grpc.ServicerContext
|
|
103
151
|
) -> Generator[StreamLogsResponse, Any, None]:
|
|
104
152
|
"""Get logs."""
|
|
105
|
-
log(INFO, "
|
|
153
|
+
log(INFO, "ControlServicer.StreamLogs")
|
|
106
154
|
state = self.linkstate_factory.state()
|
|
107
155
|
|
|
108
156
|
# Retrieve run ID and run
|
|
@@ -149,7 +197,7 @@ class ExecServicer(exec_pb2_grpc.ExecServicer):
|
|
|
149
197
|
self, request: ListRunsRequest, context: grpc.ServicerContext
|
|
150
198
|
) -> ListRunsResponse:
|
|
151
199
|
"""Handle `flwr ls` command."""
|
|
152
|
-
log(INFO, "
|
|
200
|
+
log(INFO, "ControlServicer.List")
|
|
153
201
|
state = self.linkstate_factory.state()
|
|
154
202
|
|
|
155
203
|
# Build a set of run IDs for `flwr ls --runs`
|
|
@@ -195,7 +243,7 @@ class ExecServicer(exec_pb2_grpc.ExecServicer):
|
|
|
195
243
|
self, request: StopRunRequest, context: grpc.ServicerContext
|
|
196
244
|
) -> StopRunResponse:
|
|
197
245
|
"""Stop a given run ID."""
|
|
198
|
-
log(INFO, "
|
|
246
|
+
log(INFO, "ControlServicer.StopRun")
|
|
199
247
|
state = self.linkstate_factory.state()
|
|
200
248
|
|
|
201
249
|
# Retrieve run ID and run
|
|
@@ -240,11 +288,11 @@ class ExecServicer(exec_pb2_grpc.ExecServicer):
|
|
|
240
288
|
self, request: GetLoginDetailsRequest, context: grpc.ServicerContext
|
|
241
289
|
) -> GetLoginDetailsResponse:
|
|
242
290
|
"""Start login."""
|
|
243
|
-
log(INFO, "
|
|
291
|
+
log(INFO, "ControlServicer.GetLoginDetails")
|
|
244
292
|
if self.auth_plugin is None:
|
|
245
293
|
context.abort(
|
|
246
294
|
grpc.StatusCode.UNIMPLEMENTED,
|
|
247
|
-
|
|
295
|
+
NO_USER_AUTH_MESSAGE,
|
|
248
296
|
)
|
|
249
297
|
raise grpc.RpcError() # This line is unreachable
|
|
250
298
|
|
|
@@ -267,11 +315,11 @@ class ExecServicer(exec_pb2_grpc.ExecServicer):
|
|
|
267
315
|
self, request: GetAuthTokensRequest, context: grpc.ServicerContext
|
|
268
316
|
) -> GetAuthTokensResponse:
|
|
269
317
|
"""Get auth token."""
|
|
270
|
-
log(INFO, "
|
|
318
|
+
log(INFO, "ControlServicer.GetAuthTokens")
|
|
271
319
|
if self.auth_plugin is None:
|
|
272
320
|
context.abort(
|
|
273
321
|
grpc.StatusCode.UNIMPLEMENTED,
|
|
274
|
-
|
|
322
|
+
NO_USER_AUTH_MESSAGE,
|
|
275
323
|
)
|
|
276
324
|
raise grpc.RpcError() # This line is unreachable
|
|
277
325
|
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
# ==============================================================================
|
|
15
|
-
"""Flower
|
|
15
|
+
"""Flower Control API interceptor."""
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
import contextvars
|
|
@@ -20,9 +20,9 @@ from typing import Any, Callable, Union
|
|
|
20
20
|
|
|
21
21
|
import grpc
|
|
22
22
|
|
|
23
|
-
from flwr.common.auth_plugin import
|
|
23
|
+
from flwr.common.auth_plugin import ControlAuthPlugin, ControlAuthzPlugin
|
|
24
24
|
from flwr.common.typing import AccountInfo
|
|
25
|
-
from flwr.proto.
|
|
25
|
+
from flwr.proto.control_pb2 import ( # pylint: disable=E0611
|
|
26
26
|
GetAuthTokensRequest,
|
|
27
27
|
GetAuthTokensResponse,
|
|
28
28
|
GetLoginDetailsRequest,
|
|
@@ -50,13 +50,13 @@ shared_account_info: contextvars.ContextVar[AccountInfo] = contextvars.ContextVa
|
|
|
50
50
|
)
|
|
51
51
|
|
|
52
52
|
|
|
53
|
-
class
|
|
54
|
-
"""
|
|
53
|
+
class ControlUserAuthInterceptor(grpc.ServerInterceptor): # type: ignore
|
|
54
|
+
"""Control API interceptor for user authentication."""
|
|
55
55
|
|
|
56
56
|
def __init__(
|
|
57
57
|
self,
|
|
58
|
-
auth_plugin:
|
|
59
|
-
authz_plugin:
|
|
58
|
+
auth_plugin: ControlAuthPlugin,
|
|
59
|
+
authz_plugin: ControlAuthzPlugin,
|
|
60
60
|
):
|
|
61
61
|
self.auth_plugin = auth_plugin
|
|
62
62
|
self.authz_plugin = authz_plugin
|
|
@@ -72,8 +72,12 @@ class ExecUserAuthInterceptor(grpc.ServerInterceptor): # type: ignore
|
|
|
72
72
|
by validating auth metadata sent by the user. Continue RPC call if user is
|
|
73
73
|
authenticated, else, terminate RPC call by setting context to abort.
|
|
74
74
|
"""
|
|
75
|
+
# Only apply to Control service
|
|
76
|
+
if not handler_call_details.method.startswith("/flwr.proto.Control/"):
|
|
77
|
+
return continuation(handler_call_details)
|
|
78
|
+
|
|
75
79
|
# One of the method handlers in
|
|
76
|
-
# `flwr.
|
|
80
|
+
# `flwr.superlink.servicer.control.ControlServicer`
|
|
77
81
|
method_handler: grpc.RpcMethodHandler = continuation(handler_call_details)
|
|
78
82
|
return self._generic_auth_unary_method_handler(method_handler)
|
|
79
83
|
|
|
@@ -108,7 +112,9 @@ class ExecUserAuthInterceptor(grpc.ServerInterceptor): # type: ignore
|
|
|
108
112
|
# Check if the user is authorized
|
|
109
113
|
if not self.authz_plugin.verify_user_authorization(account_info):
|
|
110
114
|
context.abort(
|
|
111
|
-
grpc.StatusCode.PERMISSION_DENIED,
|
|
115
|
+
grpc.StatusCode.PERMISSION_DENIED,
|
|
116
|
+
"❗️ User not authorized. "
|
|
117
|
+
"Please contact the SuperLink administrator.",
|
|
112
118
|
)
|
|
113
119
|
raise grpc.RpcError()
|
|
114
120
|
return call(request, context) # type: ignore
|
|
@@ -127,7 +133,9 @@ class ExecUserAuthInterceptor(grpc.ServerInterceptor): # type: ignore
|
|
|
127
133
|
# Check if the user is authorized
|
|
128
134
|
if not self.authz_plugin.verify_user_authorization(account_info):
|
|
129
135
|
context.abort(
|
|
130
|
-
grpc.StatusCode.PERMISSION_DENIED,
|
|
136
|
+
grpc.StatusCode.PERMISSION_DENIED,
|
|
137
|
+
"❗️ User not authorized. "
|
|
138
|
+
"Please contact the SuperLink administrator.",
|
|
131
139
|
)
|
|
132
140
|
raise grpc.RpcError()
|
|
133
141
|
|
|
@@ -40,8 +40,8 @@ from flwr.common.constant import (
|
|
|
40
40
|
TRANSPORT_TYPE_REST,
|
|
41
41
|
)
|
|
42
42
|
from flwr.common.exit import ExitCode, flwr_exit
|
|
43
|
-
from flwr.common.exit_handlers import register_exit_handlers
|
|
44
43
|
from flwr.common.logger import log
|
|
44
|
+
from flwr.supercore.grpc_health import add_args_health
|
|
45
45
|
from flwr.supernode.start_client_internal import start_client_internal
|
|
46
46
|
|
|
47
47
|
|
|
@@ -66,12 +66,6 @@ def flower_supernode() -> None:
|
|
|
66
66
|
|
|
67
67
|
log(DEBUG, "Isolation mode: %s", args.isolation)
|
|
68
68
|
|
|
69
|
-
# Register handlers for graceful shutdown
|
|
70
|
-
register_exit_handlers(
|
|
71
|
-
event_type=EventType.RUN_SUPERNODE_LEAVE,
|
|
72
|
-
exit_message="SuperNode terminated gracefully.",
|
|
73
|
-
)
|
|
74
|
-
|
|
75
69
|
start_client_internal(
|
|
76
70
|
server_address=args.superlink,
|
|
77
71
|
transport=args.transport,
|
|
@@ -86,6 +80,7 @@ def flower_supernode() -> None:
|
|
|
86
80
|
flwr_path=args.flwr_dir,
|
|
87
81
|
isolation=args.isolation,
|
|
88
82
|
clientappio_api_address=args.clientappio_api_address,
|
|
83
|
+
health_server_address=args.health_server_address,
|
|
89
84
|
)
|
|
90
85
|
|
|
91
86
|
|
|
@@ -125,6 +120,7 @@ def _parse_args_run_supernode() -> argparse.ArgumentParser:
|
|
|
125
120
|
help="ClientAppIo API (gRPC) server address (IPv4, IPv6, or a domain name). "
|
|
126
121
|
f"By default, it is set to {CLIENTAPPIO_API_DEFAULT_SERVER_ADDRESS}.",
|
|
127
122
|
)
|
|
123
|
+
add_args_health(parser)
|
|
128
124
|
|
|
129
125
|
return parser
|
|
130
126
|
|
|
@@ -19,9 +19,13 @@ import argparse
|
|
|
19
19
|
from logging import DEBUG, INFO
|
|
20
20
|
|
|
21
21
|
from flwr.common.args import add_args_flwr_app_common
|
|
22
|
-
from flwr.common.constant import CLIENTAPPIO_API_DEFAULT_CLIENT_ADDRESS
|
|
22
|
+
from flwr.common.constant import CLIENTAPPIO_API_DEFAULT_CLIENT_ADDRESS, ExecPluginType
|
|
23
23
|
from flwr.common.exit import ExitCode, flwr_exit
|
|
24
24
|
from flwr.common.logger import log
|
|
25
|
+
from flwr.proto.clientappio_pb2_grpc import ClientAppIoStub
|
|
26
|
+
from flwr.supercore.superexec.plugin import ClientAppExecPlugin
|
|
27
|
+
from flwr.supercore.superexec.run_superexec import run_with_deprecation_warning
|
|
28
|
+
from flwr.supercore.utils import mask_string
|
|
25
29
|
from flwr.supernode.runtime.run_clientapp import run_clientapp
|
|
26
30
|
|
|
27
31
|
|
|
@@ -34,17 +38,30 @@ def flwr_clientapp() -> None:
|
|
|
34
38
|
"flwr-clientapp does not support TLS yet.",
|
|
35
39
|
)
|
|
36
40
|
|
|
41
|
+
# Disallow long-running `flwr-clientapp` processes
|
|
42
|
+
if args.token is None:
|
|
43
|
+
run_with_deprecation_warning(
|
|
44
|
+
cmd="flwr-clientapp",
|
|
45
|
+
plugin_type=ExecPluginType.CLIENT_APP,
|
|
46
|
+
plugin_class=ClientAppExecPlugin,
|
|
47
|
+
stub_class=ClientAppIoStub,
|
|
48
|
+
appio_api_address=args.clientappio_api_address,
|
|
49
|
+
flwr_dir=args.flwr_dir,
|
|
50
|
+
parent_pid=args.parent_pid,
|
|
51
|
+
warn_run_once=args.run_once,
|
|
52
|
+
)
|
|
53
|
+
return
|
|
54
|
+
|
|
37
55
|
log(INFO, "Start `flwr-clientapp` process")
|
|
38
56
|
log(
|
|
39
57
|
DEBUG,
|
|
40
58
|
"`flwr-clientapp` will attempt to connect to SuperNode's "
|
|
41
59
|
"ClientAppIo API at %s with token %s",
|
|
42
60
|
args.clientappio_api_address,
|
|
43
|
-
args.token,
|
|
61
|
+
mask_string(args.token) if args.token else "None",
|
|
44
62
|
)
|
|
45
63
|
run_clientapp(
|
|
46
64
|
clientappio_api_address=args.clientappio_api_address,
|
|
47
|
-
run_once=(args.token is not None),
|
|
48
65
|
token=args.token,
|
|
49
66
|
flwr_dir=args.flwr_dir,
|
|
50
67
|
certificates=None,
|
|
@@ -64,18 +81,5 @@ def _parse_args_run_flwr_clientapp() -> argparse.ArgumentParser:
|
|
|
64
81
|
help="Address of SuperNode's ClientAppIo API (IPv4, IPv6, or a domain name)."
|
|
65
82
|
f"By default, it is set to {CLIENTAPPIO_API_DEFAULT_CLIENT_ADDRESS}.",
|
|
66
83
|
)
|
|
67
|
-
parser.add_argument(
|
|
68
|
-
"--token",
|
|
69
|
-
type=int,
|
|
70
|
-
required=False,
|
|
71
|
-
help="Unique token generated by SuperNode for each ClientApp execution",
|
|
72
|
-
)
|
|
73
|
-
parser.add_argument(
|
|
74
|
-
"--parent-pid",
|
|
75
|
-
type=int,
|
|
76
|
-
default=None,
|
|
77
|
-
help="The PID of the parent process. When set, the process will terminate "
|
|
78
|
-
"when the parent process exits.",
|
|
79
|
-
)
|
|
80
84
|
add_args_flwr_app_common(parser=parser)
|
|
81
85
|
return parser
|
|
@@ -51,8 +51,9 @@ class InMemoryNodeState(NodeState): # pylint: disable=too-many-instance-attribu
|
|
|
51
51
|
# Store run ID to Context mapping
|
|
52
52
|
self.ctx_store: dict[int, Context] = {}
|
|
53
53
|
self.lock_ctx_store = Lock()
|
|
54
|
-
# Store run ID to token mapping
|
|
54
|
+
# Store run ID to token mapping and token to run ID mapping
|
|
55
55
|
self.token_store: dict[int, str] = {}
|
|
56
|
+
self.token_to_run_id: dict[str, int] = {}
|
|
56
57
|
self.lock_token_store = Lock()
|
|
57
58
|
|
|
58
59
|
def set_node_id(self, node_id: Optional[int]) -> None:
|
|
@@ -170,13 +171,14 @@ class InMemoryNodeState(NodeState): # pylint: disable=too-many-instance-attribu
|
|
|
170
171
|
ret -= set(self.token_store.keys())
|
|
171
172
|
return list(ret)
|
|
172
173
|
|
|
173
|
-
def create_token(self, run_id: int) -> str:
|
|
174
|
+
def create_token(self, run_id: int) -> Optional[str]:
|
|
174
175
|
"""Create a token for the given run ID."""
|
|
175
176
|
token = secrets.token_hex(FLWR_APP_TOKEN_LENGTH) # Generate a random token
|
|
176
177
|
with self.lock_token_store:
|
|
177
178
|
if run_id in self.token_store:
|
|
178
|
-
|
|
179
|
+
return None # Token already created for this run ID
|
|
179
180
|
self.token_store[run_id] = token
|
|
181
|
+
self.token_to_run_id[token] = run_id
|
|
180
182
|
return token
|
|
181
183
|
|
|
182
184
|
def verify_token(self, run_id: int, token: str) -> bool:
|
|
@@ -187,4 +189,11 @@ class InMemoryNodeState(NodeState): # pylint: disable=too-many-instance-attribu
|
|
|
187
189
|
def delete_token(self, run_id: int) -> None:
|
|
188
190
|
"""Delete the token for the given run ID."""
|
|
189
191
|
with self.lock_token_store:
|
|
190
|
-
self.token_store.pop(run_id, None)
|
|
192
|
+
token = self.token_store.pop(run_id, None)
|
|
193
|
+
if token is not None:
|
|
194
|
+
self.token_to_run_id.pop(token, None)
|
|
195
|
+
|
|
196
|
+
def get_run_id_by_token(self, token: str) -> Optional[int]:
|
|
197
|
+
"""Get the run ID associated with a given token."""
|
|
198
|
+
with self.lock_token_store:
|
|
199
|
+
return self.token_to_run_id.get(token)
|
|
@@ -15,15 +15,16 @@
|
|
|
15
15
|
"""Abstract base class NodeState."""
|
|
16
16
|
|
|
17
17
|
|
|
18
|
-
from abc import
|
|
18
|
+
from abc import abstractmethod
|
|
19
19
|
from collections.abc import Sequence
|
|
20
20
|
from typing import Optional
|
|
21
21
|
|
|
22
22
|
from flwr.common import Context, Message
|
|
23
23
|
from flwr.common.typing import Run
|
|
24
|
+
from flwr.supercore.corestate import CoreState
|
|
24
25
|
|
|
25
26
|
|
|
26
|
-
class NodeState(
|
|
27
|
+
class NodeState(CoreState):
|
|
27
28
|
"""Abstract base class for node state."""
|
|
28
29
|
|
|
29
30
|
@abstractmethod
|
|
@@ -168,45 +169,3 @@ class NodeState(ABC):
|
|
|
168
169
|
Sequence[int]
|
|
169
170
|
Sequence of run IDs with pending messages.
|
|
170
171
|
"""
|
|
171
|
-
|
|
172
|
-
@abstractmethod
|
|
173
|
-
def create_token(self, run_id: int) -> str:
|
|
174
|
-
"""Create a token for the given run ID.
|
|
175
|
-
|
|
176
|
-
Parameters
|
|
177
|
-
----------
|
|
178
|
-
run_id : int
|
|
179
|
-
The ID of the run for which to create a token.
|
|
180
|
-
|
|
181
|
-
Returns
|
|
182
|
-
-------
|
|
183
|
-
str
|
|
184
|
-
A unique token associated with the run ID.
|
|
185
|
-
"""
|
|
186
|
-
|
|
187
|
-
@abstractmethod
|
|
188
|
-
def verify_token(self, run_id: int, token: str) -> bool:
|
|
189
|
-
"""Verify a token for the given run ID.
|
|
190
|
-
|
|
191
|
-
Parameters
|
|
192
|
-
----------
|
|
193
|
-
run_id : int
|
|
194
|
-
The ID of the run for which to verify the token.
|
|
195
|
-
token : str
|
|
196
|
-
The token to verify.
|
|
197
|
-
|
|
198
|
-
Returns
|
|
199
|
-
-------
|
|
200
|
-
bool
|
|
201
|
-
True if the token is valid for the run ID, False otherwise.
|
|
202
|
-
"""
|
|
203
|
-
|
|
204
|
-
@abstractmethod
|
|
205
|
-
def delete_token(self, run_id: int) -> None:
|
|
206
|
-
"""Delete the token for the given run ID.
|
|
207
|
-
|
|
208
|
-
Parameters
|
|
209
|
-
----------
|
|
210
|
-
run_id : int
|
|
211
|
-
The ID of the run for which to delete the token.
|
|
212
|
-
"""
|