flwr 1.20.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/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 +11 -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 +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 +1 -1
- flwr/cli/new/templates/app/pyproject.pytorch_msg_api.toml.tpl +53 -0
- 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 +9 -13
- flwr/cli/stop.py +7 -4
- flwr/cli/utils.py +19 -8
- flwr/client/grpc_rere_client/connection.py +1 -12
- flwr/client/rest_client/connection.py +3 -0
- 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 +23 -4
- 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 +26 -7
- flwr/common/exit/exit_handler.py +62 -0
- flwr/common/{exit_handlers.py → exit/signal_handler.py} +20 -37
- flwr/common/grpc.py +0 -11
- flwr/common/inflatable_utils.py +1 -1
- flwr/common/logger.py +1 -1
- flwr/common/retry_invoker.py +30 -11
- flwr/common/telemetry.py +4 -0
- flwr/compat/server/app.py +2 -2
- flwr/proto/appio_pb2.py +25 -17
- flwr/proto/appio_pb2.pyi +46 -2
- flwr/proto/clientappio_pb2.py +3 -11
- flwr/proto/clientappio_pb2.pyi +0 -47
- flwr/proto/clientappio_pb2_grpc.py +19 -20
- flwr/proto/clientappio_pb2_grpc.pyi +10 -11
- 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/serverappio_pb2.py +2 -2
- flwr/proto/serverappio_pb2_grpc.py +68 -0
- flwr/proto/serverappio_pb2_grpc.pyi +26 -0
- 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 +129 -152
- flwr/server/grid/grpc_grid.py +3 -0
- flwr/server/grid/inmemory_grid.py +1 -0
- flwr/server/serverapp/app.py +157 -146
- 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 +1 -1
- flwr/server/superlink/serverappio/serverappio_servicer.py +61 -6
- flwr/server/superlink/simulation/simulationio_servicer.py +97 -21
- 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 +161 -164
- flwr/supercore/app_utils.py +58 -0
- flwr/{supernode/scheduler → supercore/cli}/__init__.py +3 -3
- flwr/supercore/cli/flower_superexec.py +141 -0
- flwr/supercore/{scheduler → corestate}/__init__.py +3 -3
- flwr/supercore/corestate/corestate.py +81 -0
- flwr/supercore/grpc_health/__init__.py +3 -0
- flwr/supercore/grpc_health/health_server.py +53 -0
- flwr/supercore/grpc_health/simple_health_servicer.py +2 -2
- flwr/{superexec → supercore/superexec}/__init__.py +1 -1
- flwr/supercore/superexec/plugin/__init__.py +28 -0
- flwr/{supernode/scheduler/simple_clientapp_scheduler_plugin.py → supercore/superexec/plugin/base_exec_plugin.py} +10 -6
- flwr/supercore/superexec/plugin/clientapp_exec_plugin.py +28 -0
- flwr/supercore/{scheduler/plugin.py → superexec/plugin/exec_plugin.py} +4 -4
- 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/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} +7 -7
- flwr/{superexec/exec_grpc.py → superlink/servicer/control/control_grpc.py} +24 -29
- flwr/{superexec/exec_license_interceptor.py → superlink/servicer/control/control_license_interceptor.py} +6 -6
- flwr/{superexec/exec_servicer.py → superlink/servicer/control/control_servicer.py} +69 -30
- flwr/{superexec/exec_user_auth_interceptor.py → superlink/servicer/control/control_user_auth_interceptor.py} +10 -10
- flwr/supernode/cli/flower_supernode.py +3 -0
- flwr/supernode/cli/flwr_clientapp.py +18 -21
- flwr/supernode/nodestate/in_memory_nodestate.py +2 -2
- flwr/supernode/nodestate/nodestate.py +3 -59
- flwr/supernode/runtime/run_clientapp.py +39 -102
- flwr/supernode/servicer/clientappio/clientappio_servicer.py +10 -17
- flwr/supernode/start_client_internal.py +35 -76
- {flwr-1.20.0.dist-info → flwr-1.21.0.dist-info}/METADATA +4 -3
- {flwr-1.20.0.dist-info → flwr-1.21.0.dist-info}/RECORD +127 -98
- {flwr-1.20.0.dist-info → flwr-1.21.0.dist-info}/entry_points.txt +1 -0
- flwr/proto/exec_pb2.py +0 -62
- flwr/superexec/app.py +0 -45
- flwr/superexec/deployment.py +0 -191
- flwr/superexec/executor.py +0 -100
- flwr/superexec/simulation.py +0 -129
- /flwr/proto/{exec_pb2.pyi → control_pb2.pyi} +0 -0
- {flwr-1.20.0.dist-info → flwr-1.21.0.dist-info}/WHEEL +0 -0
|
@@ -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
|
-
"""
|
|
15
|
+
"""Control API server."""
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
from logging import INFO
|
|
@@ -21,23 +21,21 @@ 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
|
|
24
|
+
from flwr.common.auth_plugin import ControlAuthPlugin, ControlAuthzPlugin
|
|
25
25
|
from flwr.common.event_log_plugin import EventLogWriterPlugin
|
|
26
26
|
from flwr.common.exit import ExitCode, flwr_exit
|
|
27
27
|
from flwr.common.grpc import generic_create_grpc_server
|
|
28
28
|
from flwr.common.logger import log
|
|
29
|
-
from flwr.
|
|
30
|
-
from flwr.proto.exec_pb2_grpc import add_ExecServicer_to_server
|
|
29
|
+
from flwr.proto.control_pb2_grpc import add_ControlServicer_to_server
|
|
31
30
|
from flwr.server.superlink.linkstate import LinkStateFactory
|
|
32
31
|
from flwr.supercore.ffs import FfsFactory
|
|
33
32
|
from flwr.supercore.license_plugin import LicensePlugin
|
|
34
33
|
from flwr.supercore.object_store import ObjectStoreFactory
|
|
35
|
-
from flwr.superexec.exec_event_log_interceptor import ExecEventLogInterceptor
|
|
36
|
-
from flwr.superexec.exec_license_interceptor import ExecLicenseInterceptor
|
|
37
|
-
from flwr.superexec.exec_user_auth_interceptor import ExecUserAuthInterceptor
|
|
38
34
|
|
|
39
|
-
from .
|
|
40
|
-
from .
|
|
35
|
+
from .control_event_log_interceptor import ControlEventLogInterceptor
|
|
36
|
+
from .control_license_interceptor import ControlLicenseInterceptor
|
|
37
|
+
from .control_servicer import ControlServicer
|
|
38
|
+
from .control_user_auth_interceptor import ControlUserAuthInterceptor
|
|
41
39
|
|
|
42
40
|
try:
|
|
43
41
|
from flwr.ee import get_license_plugin
|
|
@@ -48,44 +46,41 @@ except ImportError:
|
|
|
48
46
|
|
|
49
47
|
|
|
50
48
|
# pylint: disable-next=too-many-arguments,too-many-positional-arguments,too-many-locals
|
|
51
|
-
def
|
|
49
|
+
def run_control_api_grpc(
|
|
52
50
|
address: str,
|
|
53
|
-
executor: Executor,
|
|
54
51
|
state_factory: LinkStateFactory,
|
|
55
52
|
ffs_factory: FfsFactory,
|
|
56
53
|
objectstore_factory: ObjectStoreFactory,
|
|
57
54
|
certificates: Optional[tuple[bytes, bytes, bytes]],
|
|
58
|
-
|
|
59
|
-
auth_plugin: Optional[
|
|
60
|
-
authz_plugin: Optional[
|
|
55
|
+
is_simulation: bool,
|
|
56
|
+
auth_plugin: Optional[ControlAuthPlugin] = None,
|
|
57
|
+
authz_plugin: Optional[ControlAuthzPlugin] = None,
|
|
61
58
|
event_log_plugin: Optional[EventLogWriterPlugin] = None,
|
|
62
59
|
) -> grpc.Server:
|
|
63
|
-
"""Run
|
|
64
|
-
executor.set_config(config)
|
|
65
|
-
|
|
60
|
+
"""Run Control API (gRPC, request-response)."""
|
|
66
61
|
license_plugin: Optional[LicensePlugin] = get_license_plugin()
|
|
67
62
|
if license_plugin and not license_plugin.check_license():
|
|
68
63
|
flwr_exit(ExitCode.SUPERLINK_LICENSE_INVALID)
|
|
69
64
|
|
|
70
|
-
|
|
65
|
+
control_servicer: grpc.Server = ControlServicer(
|
|
71
66
|
linkstate_factory=state_factory,
|
|
72
67
|
ffs_factory=ffs_factory,
|
|
73
68
|
objectstore_factory=objectstore_factory,
|
|
74
|
-
|
|
69
|
+
is_simulation=is_simulation,
|
|
75
70
|
auth_plugin=auth_plugin,
|
|
76
71
|
)
|
|
77
72
|
interceptors: list[grpc.ServerInterceptor] = []
|
|
78
73
|
if license_plugin is not None:
|
|
79
|
-
interceptors.append(
|
|
74
|
+
interceptors.append(ControlLicenseInterceptor(license_plugin))
|
|
80
75
|
if auth_plugin is not None and authz_plugin is not None:
|
|
81
|
-
interceptors.append(
|
|
76
|
+
interceptors.append(ControlUserAuthInterceptor(auth_plugin, authz_plugin))
|
|
82
77
|
# Event log interceptor must be added after user auth interceptor
|
|
83
78
|
if event_log_plugin is not None:
|
|
84
|
-
interceptors.append(
|
|
79
|
+
interceptors.append(ControlEventLogInterceptor(event_log_plugin))
|
|
85
80
|
log(INFO, "Flower event logging enabled")
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
servicer_and_add_fn=(
|
|
81
|
+
control_add_servicer_to_server_fn = add_ControlServicer_to_server
|
|
82
|
+
control_grpc_server = generic_create_grpc_server(
|
|
83
|
+
servicer_and_add_fn=(control_servicer, control_add_servicer_to_server_fn),
|
|
89
84
|
server_address=address,
|
|
90
85
|
max_message_length=GRPC_MAX_MESSAGE_LENGTH,
|
|
91
86
|
certificates=certificates,
|
|
@@ -93,14 +88,14 @@ def run_exec_api_grpc(
|
|
|
93
88
|
)
|
|
94
89
|
|
|
95
90
|
if auth_plugin is None:
|
|
96
|
-
log(INFO, "Flower Deployment
|
|
91
|
+
log(INFO, "Flower Deployment Runtime: Starting Control API on %s", address)
|
|
97
92
|
else:
|
|
98
93
|
log(
|
|
99
94
|
INFO,
|
|
100
|
-
"Flower Deployment
|
|
95
|
+
"Flower Deployment Runtime: Starting Control API with user "
|
|
101
96
|
"authentication on %s",
|
|
102
97
|
address,
|
|
103
98
|
)
|
|
104
|
-
|
|
99
|
+
control_grpc_server.start()
|
|
105
100
|
|
|
106
|
-
return
|
|
101
|
+
return control_grpc_server
|
|
@@ -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 license interceptor."""
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
from collections.abc import Iterator
|
|
@@ -24,8 +24,8 @@ from google.protobuf.message import Message as GrpcMessage
|
|
|
24
24
|
from flwr.supercore.license_plugin import LicensePlugin
|
|
25
25
|
|
|
26
26
|
|
|
27
|
-
class
|
|
28
|
-
"""
|
|
27
|
+
class ControlLicenseInterceptor(grpc.ServerInterceptor): # type: ignore
|
|
28
|
+
"""Control API interceptor for license checking."""
|
|
29
29
|
|
|
30
30
|
def __init__(self, license_plugin: LicensePlugin) -> None:
|
|
31
31
|
"""Initialize the interceptor with a license plugin."""
|
|
@@ -42,12 +42,12 @@ class ExecLicenseInterceptor(grpc.ServerInterceptor): # type: ignore
|
|
|
42
42
|
Continue RPC call if license check is enabled and passes, else, terminate RPC
|
|
43
43
|
call by setting context to abort.
|
|
44
44
|
"""
|
|
45
|
-
# Only apply to
|
|
46
|
-
if not handler_call_details.method.startswith("/flwr.proto.
|
|
45
|
+
# Only apply to Control service
|
|
46
|
+
if not handler_call_details.method.startswith("/flwr.proto.Control/"):
|
|
47
47
|
return continuation(handler_call_details)
|
|
48
48
|
|
|
49
49
|
# One of the method handlers in
|
|
50
|
-
# `flwr.
|
|
50
|
+
# `flwr.superlink.servicer.control.ControlServicer`
|
|
51
51
|
method_handler: grpc.RpcMethodHandler = continuation(handler_call_details)
|
|
52
52
|
return self._generic_license_unary_method_handler(method_handler)
|
|
53
53
|
|
|
@@ -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,11 +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 (
|
|
28
30
|
FAB_MAX_SIZE,
|
|
29
31
|
LOG_STREAM_INTERVAL,
|
|
32
|
+
NO_USER_AUTH_MESSAGE,
|
|
30
33
|
RUN_ID_NOT_FOUND_MESSAGE,
|
|
31
34
|
Status,
|
|
32
35
|
SubStatus,
|
|
@@ -37,9 +40,9 @@ from flwr.common.serde import (
|
|
|
37
40
|
run_to_proto,
|
|
38
41
|
user_config_from_proto,
|
|
39
42
|
)
|
|
40
|
-
from flwr.common.typing import Run, RunStatus
|
|
41
|
-
from flwr.proto import
|
|
42
|
-
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
|
|
43
46
|
GetAuthTokensRequest,
|
|
44
47
|
GetAuthTokensResponse,
|
|
45
48
|
GetLoginDetailsRequest,
|
|
@@ -57,33 +60,33 @@ from flwr.server.superlink.linkstate import LinkState, LinkStateFactory
|
|
|
57
60
|
from flwr.supercore.ffs import FfsFactory
|
|
58
61
|
from flwr.supercore.object_store import ObjectStore, ObjectStoreFactory
|
|
59
62
|
|
|
60
|
-
from .
|
|
61
|
-
from .executor import Executor
|
|
63
|
+
from .control_user_auth_interceptor import shared_account_info
|
|
62
64
|
|
|
63
65
|
|
|
64
|
-
class
|
|
65
|
-
"""
|
|
66
|
+
class ControlServicer(control_pb2_grpc.ControlServicer):
|
|
67
|
+
"""Control API servicer."""
|
|
66
68
|
|
|
67
69
|
def __init__( # pylint: disable=R0913, R0917
|
|
68
70
|
self,
|
|
69
71
|
linkstate_factory: LinkStateFactory,
|
|
70
72
|
ffs_factory: FfsFactory,
|
|
71
73
|
objectstore_factory: ObjectStoreFactory,
|
|
72
|
-
|
|
73
|
-
auth_plugin: Optional[
|
|
74
|
+
is_simulation: bool,
|
|
75
|
+
auth_plugin: Optional[ControlAuthPlugin] = None,
|
|
74
76
|
) -> None:
|
|
75
77
|
self.linkstate_factory = linkstate_factory
|
|
76
78
|
self.ffs_factory = ffs_factory
|
|
77
79
|
self.objectstore_factory = objectstore_factory
|
|
78
|
-
self.
|
|
79
|
-
self.executor.initialize(linkstate_factory, ffs_factory)
|
|
80
|
+
self.is_simulation = is_simulation
|
|
80
81
|
self.auth_plugin = auth_plugin
|
|
81
82
|
|
|
82
83
|
def StartRun(
|
|
83
84
|
self, request: StartRunRequest, context: grpc.ServicerContext
|
|
84
85
|
) -> StartRunResponse:
|
|
85
86
|
"""Create run ID."""
|
|
86
|
-
log(INFO, "
|
|
87
|
+
log(INFO, "ControlServicer.StartRun")
|
|
88
|
+
state = self.linkstate_factory.state()
|
|
89
|
+
ffs = self.ffs_factory.ffs()
|
|
87
90
|
|
|
88
91
|
if len(request.fab.content) > FAB_MAX_SIZE:
|
|
89
92
|
log(
|
|
@@ -94,24 +97,60 @@ class ExecServicer(exec_pb2_grpc.ExecServicer):
|
|
|
94
97
|
return StartRunResponse()
|
|
95
98
|
|
|
96
99
|
flwr_aid = shared_account_info.get().flwr_aid if self.auth_plugin else None
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
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)
|
|
103
140
|
|
|
104
|
-
|
|
105
|
-
|
|
141
|
+
# pylint: disable-next=broad-except
|
|
142
|
+
except Exception as e:
|
|
143
|
+
log(ERROR, "Could not start run: %s", str(e))
|
|
106
144
|
return StartRunResponse()
|
|
107
145
|
|
|
146
|
+
log(INFO, "Created run %s", str(run_id))
|
|
108
147
|
return StartRunResponse(run_id=run_id)
|
|
109
148
|
|
|
110
149
|
def StreamLogs( # pylint: disable=C0103
|
|
111
150
|
self, request: StreamLogsRequest, context: grpc.ServicerContext
|
|
112
151
|
) -> Generator[StreamLogsResponse, Any, None]:
|
|
113
152
|
"""Get logs."""
|
|
114
|
-
log(INFO, "
|
|
153
|
+
log(INFO, "ControlServicer.StreamLogs")
|
|
115
154
|
state = self.linkstate_factory.state()
|
|
116
155
|
|
|
117
156
|
# Retrieve run ID and run
|
|
@@ -158,7 +197,7 @@ class ExecServicer(exec_pb2_grpc.ExecServicer):
|
|
|
158
197
|
self, request: ListRunsRequest, context: grpc.ServicerContext
|
|
159
198
|
) -> ListRunsResponse:
|
|
160
199
|
"""Handle `flwr ls` command."""
|
|
161
|
-
log(INFO, "
|
|
200
|
+
log(INFO, "ControlServicer.List")
|
|
162
201
|
state = self.linkstate_factory.state()
|
|
163
202
|
|
|
164
203
|
# Build a set of run IDs for `flwr ls --runs`
|
|
@@ -204,7 +243,7 @@ class ExecServicer(exec_pb2_grpc.ExecServicer):
|
|
|
204
243
|
self, request: StopRunRequest, context: grpc.ServicerContext
|
|
205
244
|
) -> StopRunResponse:
|
|
206
245
|
"""Stop a given run ID."""
|
|
207
|
-
log(INFO, "
|
|
246
|
+
log(INFO, "ControlServicer.StopRun")
|
|
208
247
|
state = self.linkstate_factory.state()
|
|
209
248
|
|
|
210
249
|
# Retrieve run ID and run
|
|
@@ -249,11 +288,11 @@ class ExecServicer(exec_pb2_grpc.ExecServicer):
|
|
|
249
288
|
self, request: GetLoginDetailsRequest, context: grpc.ServicerContext
|
|
250
289
|
) -> GetLoginDetailsResponse:
|
|
251
290
|
"""Start login."""
|
|
252
|
-
log(INFO, "
|
|
291
|
+
log(INFO, "ControlServicer.GetLoginDetails")
|
|
253
292
|
if self.auth_plugin is None:
|
|
254
293
|
context.abort(
|
|
255
294
|
grpc.StatusCode.UNIMPLEMENTED,
|
|
256
|
-
|
|
295
|
+
NO_USER_AUTH_MESSAGE,
|
|
257
296
|
)
|
|
258
297
|
raise grpc.RpcError() # This line is unreachable
|
|
259
298
|
|
|
@@ -276,11 +315,11 @@ class ExecServicer(exec_pb2_grpc.ExecServicer):
|
|
|
276
315
|
self, request: GetAuthTokensRequest, context: grpc.ServicerContext
|
|
277
316
|
) -> GetAuthTokensResponse:
|
|
278
317
|
"""Get auth token."""
|
|
279
|
-
log(INFO, "
|
|
318
|
+
log(INFO, "ControlServicer.GetAuthTokens")
|
|
280
319
|
if self.auth_plugin is None:
|
|
281
320
|
context.abort(
|
|
282
321
|
grpc.StatusCode.UNIMPLEMENTED,
|
|
283
|
-
|
|
322
|
+
NO_USER_AUTH_MESSAGE,
|
|
284
323
|
)
|
|
285
324
|
raise grpc.RpcError() # This line is unreachable
|
|
286
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,12 +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
|
|
76
|
-
if not handler_call_details.method.startswith("/flwr.proto.
|
|
75
|
+
# Only apply to Control service
|
|
76
|
+
if not handler_call_details.method.startswith("/flwr.proto.Control/"):
|
|
77
77
|
return continuation(handler_call_details)
|
|
78
78
|
|
|
79
79
|
# One of the method handlers in
|
|
80
|
-
# `flwr.
|
|
80
|
+
# `flwr.superlink.servicer.control.ControlServicer`
|
|
81
81
|
method_handler: grpc.RpcMethodHandler = continuation(handler_call_details)
|
|
82
82
|
return self._generic_auth_unary_method_handler(method_handler)
|
|
83
83
|
|
|
@@ -41,6 +41,7 @@ from flwr.common.constant import (
|
|
|
41
41
|
)
|
|
42
42
|
from flwr.common.exit import ExitCode, flwr_exit
|
|
43
43
|
from flwr.common.logger import log
|
|
44
|
+
from flwr.supercore.grpc_health import add_args_health
|
|
44
45
|
from flwr.supernode.start_client_internal import start_client_internal
|
|
45
46
|
|
|
46
47
|
|
|
@@ -79,6 +80,7 @@ def flower_supernode() -> None:
|
|
|
79
80
|
flwr_path=args.flwr_dir,
|
|
80
81
|
isolation=args.isolation,
|
|
81
82
|
clientappio_api_address=args.clientappio_api_address,
|
|
83
|
+
health_server_address=args.health_server_address,
|
|
82
84
|
)
|
|
83
85
|
|
|
84
86
|
|
|
@@ -118,6 +120,7 @@ def _parse_args_run_supernode() -> argparse.ArgumentParser:
|
|
|
118
120
|
help="ClientAppIo API (gRPC) server address (IPv4, IPv6, or a domain name). "
|
|
119
121
|
f"By default, it is set to {CLIENTAPPIO_API_DEFAULT_SERVER_ADDRESS}.",
|
|
120
122
|
)
|
|
123
|
+
add_args_health(parser)
|
|
121
124
|
|
|
122
125
|
return parser
|
|
123
126
|
|
|
@@ -19,9 +19,12 @@ 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
|
|
25
28
|
from flwr.supercore.utils import mask_string
|
|
26
29
|
from flwr.supernode.runtime.run_clientapp import run_clientapp
|
|
27
30
|
|
|
@@ -35,6 +38,20 @@ def flwr_clientapp() -> None:
|
|
|
35
38
|
"flwr-clientapp does not support TLS yet.",
|
|
36
39
|
)
|
|
37
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
|
+
|
|
38
55
|
log(INFO, "Start `flwr-clientapp` process")
|
|
39
56
|
log(
|
|
40
57
|
DEBUG,
|
|
@@ -45,7 +62,6 @@ def flwr_clientapp() -> None:
|
|
|
45
62
|
)
|
|
46
63
|
run_clientapp(
|
|
47
64
|
clientappio_api_address=args.clientappio_api_address,
|
|
48
|
-
run_once=(args.token is not None) or args.run_once,
|
|
49
65
|
token=args.token,
|
|
50
66
|
flwr_dir=args.flwr_dir,
|
|
51
67
|
certificates=None,
|
|
@@ -65,24 +81,5 @@ def _parse_args_run_flwr_clientapp() -> argparse.ArgumentParser:
|
|
|
65
81
|
help="Address of SuperNode's ClientAppIo API (IPv4, IPv6, or a domain name)."
|
|
66
82
|
f"By default, it is set to {CLIENTAPPIO_API_DEFAULT_CLIENT_ADDRESS}.",
|
|
67
83
|
)
|
|
68
|
-
parser.add_argument(
|
|
69
|
-
"--token",
|
|
70
|
-
type=str,
|
|
71
|
-
required=False,
|
|
72
|
-
help="Unique token generated by SuperNode for each ClientApp execution",
|
|
73
|
-
)
|
|
74
|
-
parser.add_argument(
|
|
75
|
-
"--parent-pid",
|
|
76
|
-
type=int,
|
|
77
|
-
default=None,
|
|
78
|
-
help="The PID of the parent process. When set, the process will terminate "
|
|
79
|
-
"when the parent process exits.",
|
|
80
|
-
)
|
|
81
|
-
parser.add_argument(
|
|
82
|
-
"--run-once",
|
|
83
|
-
action="store_true",
|
|
84
|
-
help="When set, this process will start a single ClientApp for a pending "
|
|
85
|
-
"message. If there is no pending message, the process will exit.",
|
|
86
|
-
)
|
|
87
84
|
add_args_flwr_app_common(parser=parser)
|
|
88
85
|
return parser
|
|
@@ -171,12 +171,12 @@ class InMemoryNodeState(NodeState): # pylint: disable=too-many-instance-attribu
|
|
|
171
171
|
ret -= set(self.token_store.keys())
|
|
172
172
|
return list(ret)
|
|
173
173
|
|
|
174
|
-
def create_token(self, run_id: int) -> str:
|
|
174
|
+
def create_token(self, run_id: int) -> Optional[str]:
|
|
175
175
|
"""Create a token for the given run ID."""
|
|
176
176
|
token = secrets.token_hex(FLWR_APP_TOKEN_LENGTH) # Generate a random token
|
|
177
177
|
with self.lock_token_store:
|
|
178
178
|
if run_id in self.token_store:
|
|
179
|
-
|
|
179
|
+
return None # Token already created for this run ID
|
|
180
180
|
self.token_store[run_id] = token
|
|
181
181
|
self.token_to_run_id[token] = run_id
|
|
182
182
|
return 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,60 +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
|
-
"""
|
|
213
|
-
|
|
214
|
-
@abstractmethod
|
|
215
|
-
def get_run_id_by_token(self, token: str) -> Optional[int]:
|
|
216
|
-
"""Get the run ID associated with a given token.
|
|
217
|
-
|
|
218
|
-
Parameters
|
|
219
|
-
----------
|
|
220
|
-
token : str
|
|
221
|
-
The token to look up.
|
|
222
|
-
|
|
223
|
-
Returns
|
|
224
|
-
-------
|
|
225
|
-
Optional[int]
|
|
226
|
-
The run ID if the token is valid, otherwise None.
|
|
227
|
-
"""
|