flwr 1.18.0__py3-none-any.whl → 1.20.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/app/__init__.py +15 -0
- flwr/app/error.py +68 -0
- flwr/app/metadata.py +223 -0
- flwr/cli/build.py +94 -59
- flwr/cli/log.py +3 -3
- flwr/cli/login/login.py +3 -7
- flwr/cli/ls.py +15 -36
- flwr/cli/new/new.py +12 -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/client.baseline.py.tpl +1 -1
- flwr/cli/new/templates/app/code/model.baseline.py.tpl +1 -1
- flwr/cli/new/templates/app/code/server.baseline.py.tpl +2 -3
- flwr/cli/new/templates/app/pyproject.baseline.toml.tpl +25 -17
- 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.sklearn.toml.tpl +18 -1
- flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl +18 -1
- flwr/cli/run/run.py +48 -49
- flwr/cli/stop.py +2 -2
- flwr/cli/utils.py +38 -5
- flwr/client/__init__.py +2 -2
- flwr/client/client_app.py +1 -1
- flwr/client/clientapp/__init__.py +0 -7
- flwr/client/grpc_adapter_client/connection.py +15 -8
- flwr/client/grpc_rere_client/connection.py +142 -97
- flwr/client/grpc_rere_client/grpc_adapter.py +34 -6
- flwr/client/message_handler/message_handler.py +1 -1
- flwr/client/mod/comms_mods.py +36 -17
- flwr/client/rest_client/connection.py +176 -103
- flwr/clientapp/__init__.py +15 -0
- flwr/common/__init__.py +2 -2
- flwr/common/auth_plugin/__init__.py +2 -0
- flwr/common/auth_plugin/auth_plugin.py +29 -3
- flwr/common/constant.py +39 -8
- flwr/common/event_log_plugin/event_log_plugin.py +3 -3
- flwr/common/exit/exit_code.py +16 -1
- flwr/common/exit_handlers.py +30 -0
- flwr/common/grpc.py +12 -1
- flwr/common/heartbeat.py +165 -0
- flwr/common/inflatable.py +290 -0
- flwr/common/inflatable_protobuf_utils.py +141 -0
- flwr/common/inflatable_utils.py +508 -0
- flwr/common/message.py +110 -242
- flwr/common/record/__init__.py +2 -1
- flwr/common/record/array.py +402 -0
- flwr/common/record/arraychunk.py +59 -0
- flwr/common/record/arrayrecord.py +103 -225
- flwr/common/record/configrecord.py +59 -4
- flwr/common/record/conversion_utils.py +1 -1
- flwr/common/record/metricrecord.py +55 -4
- flwr/common/record/recorddict.py +69 -1
- flwr/common/recorddict_compat.py +2 -2
- flwr/common/retry_invoker.py +5 -1
- flwr/common/serde.py +59 -211
- flwr/common/serde_utils.py +175 -0
- flwr/common/typing.py +5 -3
- flwr/compat/__init__.py +15 -0
- flwr/compat/client/__init__.py +15 -0
- flwr/{client → compat/client}/app.py +28 -185
- flwr/compat/common/__init__.py +15 -0
- flwr/compat/server/__init__.py +15 -0
- flwr/compat/server/app.py +174 -0
- flwr/compat/simulation/__init__.py +15 -0
- flwr/proto/appio_pb2.py +43 -0
- flwr/proto/appio_pb2.pyi +151 -0
- flwr/proto/appio_pb2_grpc.py +4 -0
- flwr/proto/appio_pb2_grpc.pyi +4 -0
- flwr/proto/clientappio_pb2.py +12 -19
- flwr/proto/clientappio_pb2.pyi +23 -101
- flwr/proto/clientappio_pb2_grpc.py +269 -28
- flwr/proto/clientappio_pb2_grpc.pyi +114 -20
- flwr/proto/fleet_pb2.py +24 -27
- flwr/proto/fleet_pb2.pyi +19 -35
- flwr/proto/fleet_pb2_grpc.py +117 -13
- flwr/proto/fleet_pb2_grpc.pyi +47 -6
- flwr/proto/heartbeat_pb2.py +33 -0
- flwr/proto/heartbeat_pb2.pyi +66 -0
- flwr/proto/heartbeat_pb2_grpc.py +4 -0
- flwr/proto/heartbeat_pb2_grpc.pyi +4 -0
- flwr/proto/message_pb2.py +28 -11
- flwr/proto/message_pb2.pyi +125 -0
- flwr/proto/recorddict_pb2.py +16 -28
- flwr/proto/recorddict_pb2.pyi +46 -64
- flwr/proto/run_pb2.py +24 -32
- flwr/proto/run_pb2.pyi +4 -52
- flwr/proto/serverappio_pb2.py +9 -23
- flwr/proto/serverappio_pb2.pyi +0 -110
- flwr/proto/serverappio_pb2_grpc.py +177 -72
- flwr/proto/serverappio_pb2_grpc.pyi +75 -33
- flwr/proto/simulationio_pb2.py +12 -11
- flwr/proto/simulationio_pb2_grpc.py +35 -0
- flwr/proto/simulationio_pb2_grpc.pyi +14 -0
- flwr/server/__init__.py +1 -1
- flwr/server/app.py +69 -187
- flwr/server/compat/app_utils.py +50 -28
- flwr/server/fleet_event_log_interceptor.py +6 -2
- flwr/server/grid/grpc_grid.py +148 -41
- flwr/server/grid/inmemory_grid.py +5 -4
- flwr/server/serverapp/app.py +45 -17
- flwr/server/superlink/fleet/grpc_adapter/grpc_adapter_servicer.py +21 -3
- flwr/server/superlink/fleet/grpc_rere/fleet_servicer.py +102 -8
- flwr/server/superlink/fleet/grpc_rere/server_interceptor.py +2 -5
- flwr/server/superlink/fleet/message_handler/message_handler.py +130 -19
- flwr/server/superlink/fleet/rest_rere/rest_api.py +73 -13
- flwr/server/superlink/fleet/vce/vce_api.py +6 -3
- flwr/server/superlink/linkstate/in_memory_linkstate.py +138 -43
- flwr/server/superlink/linkstate/linkstate.py +53 -20
- flwr/server/superlink/linkstate/sqlite_linkstate.py +149 -55
- flwr/server/superlink/linkstate/utils.py +33 -29
- flwr/server/superlink/serverappio/serverappio_grpc.py +4 -1
- flwr/server/superlink/serverappio/serverappio_servicer.py +230 -84
- flwr/server/superlink/simulation/simulationio_grpc.py +1 -1
- flwr/server/superlink/simulation/simulationio_servicer.py +26 -2
- flwr/server/superlink/utils.py +9 -2
- flwr/server/utils/validator.py +2 -2
- flwr/serverapp/__init__.py +15 -0
- flwr/simulation/app.py +25 -0
- flwr/simulation/run_simulation.py +17 -0
- flwr/supercore/__init__.py +15 -0
- flwr/{server/superlink → supercore}/ffs/__init__.py +2 -0
- flwr/{server/superlink → supercore}/ffs/disk_ffs.py +1 -1
- flwr/supercore/grpc_health/__init__.py +22 -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/__init__.py +24 -0
- flwr/supercore/object_store/in_memory_object_store.py +229 -0
- flwr/supercore/object_store/object_store.py +170 -0
- flwr/supercore/object_store/object_store_factory.py +44 -0
- flwr/supercore/object_store/utils.py +43 -0
- flwr/supercore/scheduler/__init__.py +22 -0
- flwr/supercore/scheduler/plugin.py +71 -0
- flwr/{client/nodestate/nodestate.py → supercore/utils.py} +14 -13
- flwr/superexec/deployment.py +7 -4
- flwr/superexec/exec_event_log_interceptor.py +8 -4
- flwr/superexec/exec_grpc.py +25 -5
- flwr/superexec/exec_license_interceptor.py +82 -0
- flwr/superexec/exec_servicer.py +135 -24
- flwr/superexec/exec_user_auth_interceptor.py +45 -8
- flwr/superexec/executor.py +5 -1
- flwr/superexec/simulation.py +8 -3
- flwr/superlink/__init__.py +15 -0
- flwr/{client/supernode → supernode}/__init__.py +0 -7
- flwr/supernode/cli/__init__.py +24 -0
- flwr/{client/supernode/app.py → supernode/cli/flower_supernode.py} +3 -19
- flwr/supernode/cli/flwr_clientapp.py +88 -0
- flwr/supernode/nodestate/in_memory_nodestate.py +199 -0
- flwr/supernode/nodestate/nodestate.py +227 -0
- flwr/supernode/runtime/__init__.py +15 -0
- flwr/{client/clientapp/app.py → supernode/runtime/run_clientapp.py} +135 -89
- flwr/supernode/scheduler/__init__.py +22 -0
- flwr/supernode/scheduler/simple_clientapp_scheduler_plugin.py +49 -0
- flwr/supernode/servicer/__init__.py +15 -0
- flwr/supernode/servicer/clientappio/__init__.py +22 -0
- flwr/supernode/servicer/clientappio/clientappio_servicer.py +303 -0
- flwr/supernode/start_client_internal.py +589 -0
- {flwr-1.18.0.dist-info → flwr-1.20.0.dist-info}/METADATA +6 -4
- {flwr-1.18.0.dist-info → flwr-1.20.0.dist-info}/RECORD +171 -123
- {flwr-1.18.0.dist-info → flwr-1.20.0.dist-info}/WHEEL +1 -1
- {flwr-1.18.0.dist-info → flwr-1.20.0.dist-info}/entry_points.txt +2 -2
- flwr/client/clientapp/clientappio_servicer.py +0 -244
- flwr/client/heartbeat.py +0 -74
- flwr/client/nodestate/in_memory_nodestate.py +0 -38
- /flwr/{client → compat/client}/grpc_client/__init__.py +0 -0
- /flwr/{client → compat/client}/grpc_client/connection.py +0 -0
- /flwr/{server/superlink → supercore}/ffs/ffs.py +0 -0
- /flwr/{server/superlink → supercore}/ffs/ffs_factory.py +0 -0
- /flwr/{client → supernode}/nodestate/__init__.py +0 -0
- /flwr/{client → supernode}/nodestate/nodestate_factory.py +0 -0
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Copyright 2025 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
|
+
"""Factory class that creates ObjectStore instances."""
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
from logging import DEBUG
|
|
19
|
+
from typing import Optional
|
|
20
|
+
|
|
21
|
+
from flwr.common.logger import log
|
|
22
|
+
|
|
23
|
+
from .in_memory_object_store import InMemoryObjectStore
|
|
24
|
+
from .object_store import ObjectStore
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class ObjectStoreFactory:
|
|
28
|
+
"""Factory class that creates ObjectStore instances."""
|
|
29
|
+
|
|
30
|
+
def __init__(self) -> None:
|
|
31
|
+
self.store_instance: Optional[ObjectStore] = None
|
|
32
|
+
|
|
33
|
+
def store(self) -> ObjectStore:
|
|
34
|
+
"""Return an ObjectStore instance and create it, if necessary.
|
|
35
|
+
|
|
36
|
+
Returns
|
|
37
|
+
-------
|
|
38
|
+
ObjectStore
|
|
39
|
+
An ObjectStore instance for storing objects by object_id.
|
|
40
|
+
"""
|
|
41
|
+
if self.store_instance is None:
|
|
42
|
+
self.store_instance = InMemoryObjectStore()
|
|
43
|
+
log(DEBUG, "Using InMemoryObjectStore")
|
|
44
|
+
return self.store_instance
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Copyright 2025 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
|
+
"""Utils for ObjectStore."""
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
from typing import Union
|
|
19
|
+
|
|
20
|
+
from flwr.proto.appio_pb2 import PushAppMessagesRequest # pylint: disable=E0611
|
|
21
|
+
from flwr.proto.fleet_pb2 import PushMessagesRequest # pylint: disable=E0611
|
|
22
|
+
|
|
23
|
+
from . import ObjectStore
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def store_mapping_and_register_objects(
|
|
27
|
+
store: ObjectStore, request: Union[PushAppMessagesRequest, PushMessagesRequest]
|
|
28
|
+
) -> set[str]:
|
|
29
|
+
"""Store Message object to descendants mapping and preregister objects."""
|
|
30
|
+
if not request.messages_list:
|
|
31
|
+
return set()
|
|
32
|
+
objects_to_push: set[str] = set()
|
|
33
|
+
# Get run_id from the first message in the list
|
|
34
|
+
# All messages of a request should in the same run
|
|
35
|
+
run_id = request.messages_list[0].metadata.run_id
|
|
36
|
+
|
|
37
|
+
for object_tree in request.message_object_trees:
|
|
38
|
+
# Preregister
|
|
39
|
+
unavailable_obj_ids = store.preregister(run_id, object_tree)
|
|
40
|
+
# Keep track of objects that need to be pushed
|
|
41
|
+
objects_to_push |= set(unavailable_obj_ids)
|
|
42
|
+
|
|
43
|
+
return objects_to_push
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Copyright 2025 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 App Scheduler."""
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
from .plugin import SchedulerPlugin
|
|
19
|
+
|
|
20
|
+
__all__ = [
|
|
21
|
+
"SchedulerPlugin",
|
|
22
|
+
]
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# Copyright 2025 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
|
+
"""Abstract base class SchedulerPlugin."""
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
from abc import ABC, abstractmethod
|
|
19
|
+
from collections.abc import Sequence
|
|
20
|
+
from typing import Callable, Optional
|
|
21
|
+
|
|
22
|
+
from flwr.common.typing import Run
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class SchedulerPlugin(ABC):
|
|
26
|
+
"""Abstract base class for Scheduler plugins."""
|
|
27
|
+
|
|
28
|
+
def __init__(
|
|
29
|
+
self,
|
|
30
|
+
appio_api_address: str,
|
|
31
|
+
flwr_dir: str,
|
|
32
|
+
get_run: Callable[[int], Run],
|
|
33
|
+
) -> None:
|
|
34
|
+
self.appio_api_address = appio_api_address
|
|
35
|
+
self.flwr_dir = flwr_dir
|
|
36
|
+
self.get_run = get_run
|
|
37
|
+
|
|
38
|
+
@abstractmethod
|
|
39
|
+
def select_run_id(self, candidate_run_ids: Sequence[int]) -> Optional[int]:
|
|
40
|
+
"""Select a run ID to execute from a sequence of candidates.
|
|
41
|
+
|
|
42
|
+
A candidate run ID is one that has at least one pending message and is
|
|
43
|
+
not currently in progress (i.e., not associated with a token).
|
|
44
|
+
|
|
45
|
+
Parameters
|
|
46
|
+
----------
|
|
47
|
+
candidate_run_ids : Sequence[int]
|
|
48
|
+
A sequence of candidate run IDs to choose from.
|
|
49
|
+
|
|
50
|
+
Returns
|
|
51
|
+
-------
|
|
52
|
+
Optional[int]
|
|
53
|
+
The selected run ID, or None if no suitable candidate is found.
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
@abstractmethod
|
|
57
|
+
def launch_app(self, token: str, run_id: int) -> None:
|
|
58
|
+
"""Launch the application associated with a given run ID and token.
|
|
59
|
+
|
|
60
|
+
This method starts the application process using the given `token`.
|
|
61
|
+
The `run_id` is used solely for bookkeeping purposes, allowing any
|
|
62
|
+
scheduler implementation to associate this launch with a specific run.
|
|
63
|
+
|
|
64
|
+
Parameters
|
|
65
|
+
----------
|
|
66
|
+
token : str
|
|
67
|
+
The token required to run the application.
|
|
68
|
+
run_id : int
|
|
69
|
+
The ID of the run associated with the token, used for tracking or
|
|
70
|
+
logging purposes.
|
|
71
|
+
"""
|
|
@@ -12,20 +12,21 @@
|
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
# ==============================================================================
|
|
15
|
-
"""
|
|
15
|
+
"""Utility functions for the infrastructure."""
|
|
16
16
|
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
def mask_string(value: str, head: int = 4, tail: int = 4) -> str:
|
|
19
|
+
"""Mask a string by preserving only the head and tail characters.
|
|
20
20
|
|
|
21
|
+
Mask a string for safe display by preserving the head and tail characters,
|
|
22
|
+
and replacing the middle with '...'. Useful for logging tokens, secrets,
|
|
23
|
+
or IDs without exposing sensitive data.
|
|
21
24
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
def get_node_id(self) -> int:
|
|
31
|
-
"""Get the node ID."""
|
|
25
|
+
Notes
|
|
26
|
+
-----
|
|
27
|
+
If the string is shorter than the combined length of `head` and `tail`,
|
|
28
|
+
the original string is returned unchanged.
|
|
29
|
+
"""
|
|
30
|
+
if len(value) <= head + tail:
|
|
31
|
+
return value
|
|
32
|
+
return f"{value[:head]}...{value[-tail:]}"
|
flwr/superexec/deployment.py
CHANGED
|
@@ -31,9 +31,8 @@ from flwr.common.constant import (
|
|
|
31
31
|
)
|
|
32
32
|
from flwr.common.logger import log
|
|
33
33
|
from flwr.common.typing import Fab, RunStatus, UserConfig
|
|
34
|
-
from flwr.server.superlink.ffs import Ffs
|
|
35
|
-
from flwr.server.superlink.ffs.ffs_factory import FfsFactory
|
|
36
34
|
from flwr.server.superlink.linkstate import LinkState, LinkStateFactory
|
|
35
|
+
from flwr.supercore.ffs import Ffs, FfsFactory
|
|
37
36
|
|
|
38
37
|
from .executor import Executor
|
|
39
38
|
|
|
@@ -132,6 +131,7 @@ class DeploymentEngine(Executor):
|
|
|
132
131
|
self,
|
|
133
132
|
fab: Fab,
|
|
134
133
|
override_config: UserConfig,
|
|
134
|
+
flwr_aid: Optional[str],
|
|
135
135
|
) -> int:
|
|
136
136
|
fab_hash = self.ffs.put(fab.content, {})
|
|
137
137
|
if fab_hash != fab.hash_str:
|
|
@@ -141,7 +141,7 @@ class DeploymentEngine(Executor):
|
|
|
141
141
|
fab_id, fab_version = get_fab_metadata(fab.content)
|
|
142
142
|
|
|
143
143
|
run_id = self.linkstate.create_run(
|
|
144
|
-
fab_id, fab_version, fab_hash, override_config, ConfigRecord()
|
|
144
|
+
fab_id, fab_version, fab_hash, override_config, ConfigRecord(), flwr_aid
|
|
145
145
|
)
|
|
146
146
|
return run_id
|
|
147
147
|
|
|
@@ -161,6 +161,7 @@ class DeploymentEngine(Executor):
|
|
|
161
161
|
fab_file: bytes,
|
|
162
162
|
override_config: UserConfig,
|
|
163
163
|
federation_options: ConfigRecord,
|
|
164
|
+
flwr_aid: Optional[str],
|
|
164
165
|
) -> Optional[int]:
|
|
165
166
|
"""Start run using the Flower Deployment Engine."""
|
|
166
167
|
run_id = None
|
|
@@ -168,7 +169,9 @@ class DeploymentEngine(Executor):
|
|
|
168
169
|
|
|
169
170
|
# Call SuperLink to create run
|
|
170
171
|
run_id = self._create_run(
|
|
171
|
-
Fab(hashlib.sha256(fab_file).hexdigest(), fab_file),
|
|
172
|
+
Fab(hashlib.sha256(fab_file).hexdigest(), fab_file),
|
|
173
|
+
override_config,
|
|
174
|
+
flwr_aid,
|
|
172
175
|
)
|
|
173
176
|
|
|
174
177
|
# Register context for the Run
|
|
@@ -24,7 +24,7 @@ from google.protobuf.message import Message as GrpcMessage
|
|
|
24
24
|
from flwr.common.event_log_plugin.event_log_plugin import EventLogWriterPlugin
|
|
25
25
|
from flwr.common.typing import LogEntry
|
|
26
26
|
|
|
27
|
-
from .exec_user_auth_interceptor import
|
|
27
|
+
from .exec_user_auth_interceptor import shared_account_info
|
|
28
28
|
|
|
29
29
|
|
|
30
30
|
class ExecEventLogInterceptor(grpc.ServerInterceptor): # type: ignore
|
|
@@ -44,6 +44,10 @@ class ExecEventLogInterceptor(grpc.ServerInterceptor): # type: ignore
|
|
|
44
44
|
Continue RPC call if event logger is enabled on the SuperLink, else, terminate
|
|
45
45
|
RPC call by setting context to abort.
|
|
46
46
|
"""
|
|
47
|
+
# Only apply to Exec service
|
|
48
|
+
if not handler_call_details.method.startswith("/flwr.proto.Exec/"):
|
|
49
|
+
return continuation(handler_call_details)
|
|
50
|
+
|
|
47
51
|
# One of the method handlers in
|
|
48
52
|
# `flwr.superexec.exec_servicer.ExecServicer`
|
|
49
53
|
method_handler: grpc.RpcMethodHandler = continuation(handler_call_details)
|
|
@@ -62,7 +66,7 @@ class ExecEventLogInterceptor(grpc.ServerInterceptor): # type: ignore
|
|
|
62
66
|
log_entry = self.log_plugin.compose_log_before_event(
|
|
63
67
|
request=request,
|
|
64
68
|
context=context,
|
|
65
|
-
|
|
69
|
+
account_info=shared_account_info.get(),
|
|
66
70
|
method_name=method_name,
|
|
67
71
|
)
|
|
68
72
|
self.log_plugin.write_log(log_entry)
|
|
@@ -81,7 +85,7 @@ class ExecEventLogInterceptor(grpc.ServerInterceptor): # type: ignore
|
|
|
81
85
|
log_entry = self.log_plugin.compose_log_after_event(
|
|
82
86
|
request=request,
|
|
83
87
|
context=context,
|
|
84
|
-
|
|
88
|
+
account_info=shared_account_info.get(),
|
|
85
89
|
method_name=method_name,
|
|
86
90
|
response=unary_response or error,
|
|
87
91
|
)
|
|
@@ -111,7 +115,7 @@ class ExecEventLogInterceptor(grpc.ServerInterceptor): # type: ignore
|
|
|
111
115
|
log_entry = self.log_plugin.compose_log_after_event(
|
|
112
116
|
request=request,
|
|
113
117
|
context=context,
|
|
114
|
-
|
|
118
|
+
account_info=shared_account_info.get(),
|
|
115
119
|
method_name=method_name,
|
|
116
120
|
response=stream_response or error,
|
|
117
121
|
)
|
flwr/superexec/exec_grpc.py
CHANGED
|
@@ -21,44 +21,64 @@ from typing import Optional
|
|
|
21
21
|
import grpc
|
|
22
22
|
|
|
23
23
|
from flwr.common import GRPC_MAX_MESSAGE_LENGTH
|
|
24
|
-
from flwr.common.auth_plugin import ExecAuthPlugin
|
|
24
|
+
from flwr.common.auth_plugin import ExecAuthPlugin, ExecAuthzPlugin
|
|
25
25
|
from flwr.common.event_log_plugin import EventLogWriterPlugin
|
|
26
|
+
from flwr.common.exit import ExitCode, flwr_exit
|
|
26
27
|
from flwr.common.grpc import generic_create_grpc_server
|
|
27
28
|
from flwr.common.logger import log
|
|
28
29
|
from flwr.common.typing import UserConfig
|
|
29
30
|
from flwr.proto.exec_pb2_grpc import add_ExecServicer_to_server
|
|
30
|
-
from flwr.server.superlink.ffs.ffs_factory import FfsFactory
|
|
31
31
|
from flwr.server.superlink.linkstate import LinkStateFactory
|
|
32
|
+
from flwr.supercore.ffs import FfsFactory
|
|
33
|
+
from flwr.supercore.license_plugin import LicensePlugin
|
|
34
|
+
from flwr.supercore.object_store import ObjectStoreFactory
|
|
32
35
|
from flwr.superexec.exec_event_log_interceptor import ExecEventLogInterceptor
|
|
36
|
+
from flwr.superexec.exec_license_interceptor import ExecLicenseInterceptor
|
|
33
37
|
from flwr.superexec.exec_user_auth_interceptor import ExecUserAuthInterceptor
|
|
34
38
|
|
|
35
39
|
from .exec_servicer import ExecServicer
|
|
36
40
|
from .executor import Executor
|
|
37
41
|
|
|
42
|
+
try:
|
|
43
|
+
from flwr.ee import get_license_plugin
|
|
44
|
+
except ImportError:
|
|
38
45
|
|
|
39
|
-
|
|
46
|
+
def get_license_plugin() -> Optional[LicensePlugin]:
|
|
47
|
+
"""Return the license plugin."""
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
# pylint: disable-next=too-many-arguments,too-many-positional-arguments,too-many-locals
|
|
40
51
|
def run_exec_api_grpc(
|
|
41
52
|
address: str,
|
|
42
53
|
executor: Executor,
|
|
43
54
|
state_factory: LinkStateFactory,
|
|
44
55
|
ffs_factory: FfsFactory,
|
|
56
|
+
objectstore_factory: ObjectStoreFactory,
|
|
45
57
|
certificates: Optional[tuple[bytes, bytes, bytes]],
|
|
46
58
|
config: UserConfig,
|
|
47
59
|
auth_plugin: Optional[ExecAuthPlugin] = None,
|
|
60
|
+
authz_plugin: Optional[ExecAuthzPlugin] = None,
|
|
48
61
|
event_log_plugin: Optional[EventLogWriterPlugin] = None,
|
|
49
62
|
) -> grpc.Server:
|
|
50
63
|
"""Run Exec API (gRPC, request-response)."""
|
|
51
64
|
executor.set_config(config)
|
|
52
65
|
|
|
66
|
+
license_plugin: Optional[LicensePlugin] = get_license_plugin()
|
|
67
|
+
if license_plugin and not license_plugin.check_license():
|
|
68
|
+
flwr_exit(ExitCode.SUPERLINK_LICENSE_INVALID)
|
|
69
|
+
|
|
53
70
|
exec_servicer: grpc.Server = ExecServicer(
|
|
54
71
|
linkstate_factory=state_factory,
|
|
55
72
|
ffs_factory=ffs_factory,
|
|
73
|
+
objectstore_factory=objectstore_factory,
|
|
56
74
|
executor=executor,
|
|
57
75
|
auth_plugin=auth_plugin,
|
|
58
76
|
)
|
|
59
77
|
interceptors: list[grpc.ServerInterceptor] = []
|
|
60
|
-
if
|
|
61
|
-
interceptors.append(
|
|
78
|
+
if license_plugin is not None:
|
|
79
|
+
interceptors.append(ExecLicenseInterceptor(license_plugin))
|
|
80
|
+
if auth_plugin is not None and authz_plugin is not None:
|
|
81
|
+
interceptors.append(ExecUserAuthInterceptor(auth_plugin, authz_plugin))
|
|
62
82
|
# Event log interceptor must be added after user auth interceptor
|
|
63
83
|
if event_log_plugin is not None:
|
|
64
84
|
interceptors.append(ExecEventLogInterceptor(event_log_plugin))
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# Copyright 2025 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 Exec API license interceptor."""
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
from collections.abc import Iterator
|
|
19
|
+
from typing import Any, Callable, Union
|
|
20
|
+
|
|
21
|
+
import grpc
|
|
22
|
+
from google.protobuf.message import Message as GrpcMessage
|
|
23
|
+
|
|
24
|
+
from flwr.supercore.license_plugin import LicensePlugin
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class ExecLicenseInterceptor(grpc.ServerInterceptor): # type: ignore
|
|
28
|
+
"""Exec API interceptor for license checking."""
|
|
29
|
+
|
|
30
|
+
def __init__(self, license_plugin: LicensePlugin) -> None:
|
|
31
|
+
"""Initialize the interceptor with a license plugin."""
|
|
32
|
+
self.license_plugin = license_plugin
|
|
33
|
+
|
|
34
|
+
def intercept_service(
|
|
35
|
+
self,
|
|
36
|
+
continuation: Callable[[Any], Any],
|
|
37
|
+
handler_call_details: grpc.HandlerCallDetails,
|
|
38
|
+
) -> grpc.RpcMethodHandler:
|
|
39
|
+
"""Flower server interceptor license logic.
|
|
40
|
+
|
|
41
|
+
Intercept all unary-unary/unary-stream calls from users and check the license.
|
|
42
|
+
Continue RPC call if license check is enabled and passes, else, terminate RPC
|
|
43
|
+
call by setting context to abort.
|
|
44
|
+
"""
|
|
45
|
+
# Only apply to Exec service
|
|
46
|
+
if not handler_call_details.method.startswith("/flwr.proto.Exec/"):
|
|
47
|
+
return continuation(handler_call_details)
|
|
48
|
+
|
|
49
|
+
# One of the method handlers in
|
|
50
|
+
# `flwr.superexec.exec_servicer.ExecServicer`
|
|
51
|
+
method_handler: grpc.RpcMethodHandler = continuation(handler_call_details)
|
|
52
|
+
return self._generic_license_unary_method_handler(method_handler)
|
|
53
|
+
|
|
54
|
+
def _generic_license_unary_method_handler(
|
|
55
|
+
self, method_handler: grpc.RpcMethodHandler
|
|
56
|
+
) -> grpc.RpcMethodHandler:
|
|
57
|
+
def _generic_method_handler(
|
|
58
|
+
request: GrpcMessage,
|
|
59
|
+
context: grpc.ServicerContext,
|
|
60
|
+
) -> Union[GrpcMessage, Iterator[GrpcMessage]]:
|
|
61
|
+
"""Handle the method call with license checking."""
|
|
62
|
+
call = method_handler.unary_unary or method_handler.unary_stream
|
|
63
|
+
|
|
64
|
+
if not self.license_plugin.check_license():
|
|
65
|
+
context.abort(
|
|
66
|
+
grpc.StatusCode.PERMISSION_DENIED,
|
|
67
|
+
"❗️ License check failed. Please contact the SuperLink "
|
|
68
|
+
"administrator.",
|
|
69
|
+
)
|
|
70
|
+
raise grpc.RpcError()
|
|
71
|
+
|
|
72
|
+
return call(request, context) # type: ignore
|
|
73
|
+
|
|
74
|
+
if method_handler.unary_unary:
|
|
75
|
+
message_handler = grpc.unary_unary_rpc_method_handler
|
|
76
|
+
else:
|
|
77
|
+
message_handler = grpc.unary_stream_rpc_method_handler
|
|
78
|
+
return message_handler(
|
|
79
|
+
_generic_method_handler,
|
|
80
|
+
request_deserializer=method_handler.request_deserializer,
|
|
81
|
+
response_serializer=method_handler.response_serializer,
|
|
82
|
+
)
|